virDomainControllerDefFormat(virBufferPtr buf,
                             virDomainControllerDefPtr def,
                             unsigned int flags)
{
    const char *type = virDomainControllerTypeToString(def->type);
    const char *model = NULL;
    const char *modelName = NULL;
    g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
    g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);

    if (!type) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unexpected controller type %d"), def->type);
        return -1;
    }

    if (def->model != -1) {
        model = virDomainControllerModelTypeToString(def, def->model);

        if (!model) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("unexpected model type %d"), def->model);
            return -1;
        }
    }

    virBufferAsprintf(&attrBuf,
                      " type='%s' index='%d'",
                      type, def->idx);

    if (model)
        virBufferEscapeString(&attrBuf, " model='%s'", model);

    switch (def->type) {
    case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
        if (def->opts.vioserial.ports != -1) {
            virBufferAsprintf(&attrBuf, " ports='%d'",
                              def->opts.vioserial.ports);
        }
        if (def->opts.vioserial.vectors != -1) {
            virBufferAsprintf(&attrBuf, " vectors='%d'",
                              def->opts.vioserial.vectors);
        }
        break;

    case VIR_DOMAIN_CONTROLLER_TYPE_USB:
        if (def->opts.usbopts.ports != -1) {
            virBufferAsprintf(&attrBuf, " ports='%d'",
                              def->opts.usbopts.ports);
        }
        break;

    case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS:
        if (def->opts.xenbusopts.maxGrantFrames != -1) {
            virBufferAsprintf(&attrBuf, " maxGrantFrames='%d'",
                              def->opts.xenbusopts.maxGrantFrames);
        }
        if (def->opts.xenbusopts.maxEventChannels != -1) {
            virBufferAsprintf(&attrBuf, " maxEventChannels='%d'",
                              def->opts.xenbusopts.maxEventChannels);
        }
        break;

    default:
        break;
    }

    if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
        bool formatModelName = true;

        if (def->opts.pciopts.modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE)
            formatModelName = false;

        /* Historically, libvirt didn't support specifying a model name for
         * pci-root controllers; starting from 3.6.0, however, pSeries guests
         * use pci-root controllers with model name spapr-pci-host-bridge to
         * represent all PHBs, including the default one.
         *
         * In order to allow migration of pSeries guests from older libvirt
         * versions and back, we don't format the model name in the migratable
         * XML if it's spapr-pci-host-bridge, thus making "no model name" and
         * "spapr-pci-host-bridge model name" basically equivalent.
         *
         * The spapr-pci-host-bridge device is specific to pSeries.
         */
        if (def->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT &&
            def->opts.pciopts.modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE &&
            flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE) {
            formatModelName = false;
        }

        if (formatModelName) {
            modelName = virDomainControllerPCIModelNameTypeToString(def->opts.pciopts.modelName);
            if (!modelName) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("unexpected model name value %d"),
                               def->opts.pciopts.modelName);
                return -1;
            }
            virBufferAsprintf(&childBuf, "<model name='%s'/>\n", modelName);
        }

        if (def->opts.pciopts.chassisNr != -1 ||
            def->opts.pciopts.chassis != -1 ||
            def->opts.pciopts.port != -1 ||
            def->opts.pciopts.busNr != -1 ||
            def->opts.pciopts.targetIndex != -1 ||
            def->opts.pciopts.numaNode != -1 ||
            def->opts.pciopts.hotplug != VIR_TRISTATE_SWITCH_ABSENT) {
            virBufferAddLit(&childBuf, "<target");
            if (def->opts.pciopts.chassisNr != -1)
                virBufferAsprintf(&childBuf, " chassisNr='%d'",
                                  def->opts.pciopts.chassisNr);
            if (def->opts.pciopts.chassis != -1)
                virBufferAsprintf(&childBuf, " chassis='%d'",
                                  def->opts.pciopts.chassis);
            if (def->opts.pciopts.port != -1)
                virBufferAsprintf(&childBuf, " port='0x%x'",
                                  def->opts.pciopts.port);
            if (def->opts.pciopts.busNr != -1)
                virBufferAsprintf(&childBuf, " busNr='%d'",
                                  def->opts.pciopts.busNr);
            if (def->opts.pciopts.targetIndex != -1)
                virBufferAsprintf(&childBuf, " index='%d'",
                                  def->opts.pciopts.targetIndex);
            if (def->opts.pciopts.hotplug != VIR_TRISTATE_SWITCH_ABSENT) {
                virBufferAsprintf(&childBuf, " hotplug='%s'",
                                  virTristateSwitchTypeToString(def->opts.pciopts.hotplug));
            }
            if (def->opts.pciopts.numaNode == -1) {
                virBufferAddLit(&childBuf, "/>\n");
            } else {
                virBufferAddLit(&childBuf, ">\n");
                virBufferAdjustIndent(&childBuf, 2);
                virBufferAsprintf(&childBuf, "<node>%d</node>\n",
                                  def->opts.pciopts.numaNode);
                virBufferAdjustIndent(&childBuf, -2);
                virBufferAddLit(&childBuf, "</target>\n");
            }
        }
    }

    if (virDomainControllerDriverFormat(&childBuf, def) < 0)
        return -1;

    if (virDomainDeviceInfoFormat(&childBuf, &def->info, flags) < 0)
        return -1;

    if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI &&
        def->opts.pciopts.pcihole64) {
        virBufferAsprintf(&childBuf, "<pcihole64 unit='KiB'>%lu</"
                          "pcihole64>\n", def->opts.pciopts.pcihole64size);
    }

    virXMLFormatElement(buf, "controller", &attrBuf, &childBuf);

    return 0;
}