[Kimchi-devel] [PATCH] [Kimchi v2 1/2] Set mmio memory when GPU is attached

Jose Ricardo Ziviani joserz at linux.vnet.ibm.com
Tue Mar 1 17:12:48 UTC 2016


Signed-off-by: Jose Ricardo Ziviani <joserz at linux.vnet.ibm.com>
---
 i18n.py             |   2 +-
 model/host.py       |   4 +-
 model/vmhostdevs.py | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 140 insertions(+), 7 deletions(-)

diff --git a/i18n.py b/i18n.py
index a953b55..008e327 100644
--- a/i18n.py
+++ b/i18n.py
@@ -144,7 +144,7 @@ messages = {
                         "For AMD CPU, add 'iommu=pt iommu=1'."),
     "KCHVMHDEV0004E": _('"name" should be a device name string'),
     "KCHVMHDEV0005E": _('The device %(name)s is probably in use by the host. Unable to attach it to the guest.'),
-    "KCHVMHDEV0006E": _('Hot-plug of device %(name)s is not supported.'),
+    "KCHVMHDEV0006E": _('Hot-(un)plug of device %(name)s is not supported.'),
 
     "KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"),
     "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"),
diff --git a/model/host.py b/model/host.py
index 135c8a9..abf1191 100644
--- a/model/host.py
+++ b/model/host.py
@@ -146,9 +146,9 @@ class DevicesModel(object):
 class DeviceModel(object):
     def __init__(self, **kargs):
         self.conn = kargs['conn']
-        self.iommu_groups = self._get_iommu_groups()
+        self.iommu_groups = self.get_iommu_groups()
 
-    def _get_iommu_groups(self):
+    def get_iommu_groups(self):
         iommu_groups = defaultdict(list)
         conn = self.conn
 
diff --git a/model/vmhostdevs.py b/model/vmhostdevs.py
index dabe8e4..2a62249 100644
--- a/model/vmhostdevs.py
+++ b/model/vmhostdevs.py
@@ -22,7 +22,7 @@ import libvirt
 import os
 import platform
 from lxml import etree, objectify
-from lxml.builder import E
+from lxml.builder import E, ElementMaker
 from operator import itemgetter
 
 from wok.exception import InvalidOperation, InvalidParameter, NotFoundError
@@ -34,6 +34,12 @@ from wok.plugins.kimchi.model.config import CapabilitiesModel
 from wok.plugins.kimchi.model.host import DeviceModel, DevicesModel
 from wok.plugins.kimchi.model.utils import get_vm_config_flag
 from wok.plugins.kimchi.model.vms import DOM_STATE_MAP, VMModel
+from wok.plugins.kimchi.xmlutils.qemucmdline import get_qemucmdline_xml
+from wok.plugins.kimchi.xmlutils.qemucmdline import QEMU_NAMESPACE
+
+
+CMDLINE_FIELD_NAME = 'spapr-pci-host-bridge.mem_win_size'
+WINDOW_SIZE_BAR = 0x800000000
 
 
 class VMHostDevsModel(object):
@@ -149,6 +155,7 @@ class VMHostDevsModel(object):
 
         slots = sorted(slots)
 
+        free = 0
         for free, slot in enumerate(slots, start=1):
             if free < slot:
                 return free
@@ -184,8 +191,9 @@ class VMHostDevsModel(object):
             DOM_STATE_MAP[dom.info()[0]] == "shutoff"
         pci_infos = sorted(pci_infos, key=itemgetter('name'))
 
-        if dev_model.is_device_3D_controller(dev_info) and \
-           DOM_STATE_MAP[dom.info()[0]] != "shutoff":
+        # does not allow hot-plug of 3D graphic cards
+        is_3D_device = dev_model.is_device_3D_controller(dev_info)
+        if is_3D_device and DOM_STATE_MAP[dom.info()[0]] != "shutoff":
             raise InvalidOperation('KCHVMHDEV0006E',
                                    {'name': dev_info['name']})
 
@@ -207,6 +215,12 @@ class VMHostDevsModel(object):
 
         device_flags = get_vm_config_flag(dom, mode='all')
 
+        # when attaching a 3D graphic device it might be necessary to increase
+        # the window size memory in order to be able to attach more than one
+        # device to the same guest
+        if is_3D_device:
+            self.update_mmio_guest(vmid, True)
+
         slot = 0
         if is_multifunction:
             slot = self._available_slot(dom)
@@ -229,6 +243,115 @@ class VMHostDevsModel(object):
 
         return dev_info['name']
 
+    def _count_3D_devices_attached(self, dom):
+        counter = 0
+        root = objectify.fromstring(dom.XMLDesc(0))
+
+        try:
+            hostdev = root.devices.hostdev
+
+        except AttributeError:
+            return counter
+
+        for device in hostdev:
+            if device.attrib['type'] != 'pci':
+                continue
+
+            name = DeviceModel.deduce_dev_name(device, self.conn)
+            info = DeviceModel(conn=self.conn).lookup(name)
+            if 'vga3d' in info and info['vga3d']:
+                counter += 1
+
+        return counter
+
+    def update_mmio_guest(self, vmid, is_attaching):
+        dom = VMModel.get_vm(vmid, self.conn)
+        # get the number of 3D graphic cards already attached to the guest
+        # based on this number we will decide if the memory size will be
+        # increased or not
+        counter = self._count_3D_devices_attached(dom)
+        if counter == 0 and is_attaching:
+            return
+
+        size = 0
+        if is_attaching:
+            # suppose this is the 3rd graphic card to be attached to the same
+            # guest, counter will be 2+1 (2 existing + this attachment) times
+            # 32G (0x80000000)
+            size = hex((counter + 1) * WINDOW_SIZE_BAR)
+
+        else:
+            size = hex(counter * WINDOW_SIZE_BAR)
+
+        # if the guest already has the xml file we will simply update the
+        # value, otherwise we will add the new field
+        new_xml = self._update_win_memory_size(dom, counter, size)
+        if new_xml is None and is_attaching:
+            new_xml = self._add_win_memory_size(dom, size)
+
+        # update the XML
+        self.conn.get().defineXML(new_xml)
+
+    def _update_win_memory_size(self, dom, counter, wnd_size):
+        root = objectify.fromstring(dom.XMLDesc(0))
+
+        # look for the existing argument in <qemu:commandline> and try
+        # to update the value (or remove if there is only one (or none)
+        # graphic card attached.
+        cmdline = root.findall('{%s}commandline' % QEMU_NAMESPACE)
+        for line in cmdline:
+            for arg in line.iterchildren():
+                if not arg.values()[0].startswith(CMDLINE_FIELD_NAME):
+                    continue
+
+                # update mem_win_size value
+                if counter > 1:
+                    arg.set('value', CMDLINE_FIELD_NAME + '=' + wnd_size)
+
+                # remove mem_win_size
+                elif counter <= 1:
+                    line.remove(arg)
+
+                return etree.tostring(root, encoding='utf-8',
+                                      pretty_print=True)
+
+        return None
+
+    def _add_win_memory_size(self, dom, wnd_size):
+        root = objectify.fromstring(dom.XMLDesc(0))
+        val = CMDLINE_FIELD_NAME + '=' + wnd_size
+
+        cmdline = root.find('{%s}commandline' % QEMU_NAMESPACE)
+        # <qemu:commandline> doesn't exist, create the full commandline xml
+        # with the required values and return
+        if cmdline is None:
+            args = {}
+            args['-global'] = val
+            root.append(etree.fromstring(get_qemucmdline_xml(args)))
+            return etree.tostring(root, encoding='utf-8', pretty_print=True)
+
+        # <qemu:commandline> exists and already has the tag
+        # <qemu:arg value='-global'> (user could already been using this for
+        # something else), so we just add our <qemu:arg...> missing.
+        found = False
+        for arg in cmdline.iterchildren():
+            if arg.values()[0] == '-global':
+                EM = ElementMaker(namespace=QEMU_NAMESPACE,
+                                  nsmap={'qemu': QEMU_NAMESPACE})
+                cmdline.append(EM.arg(value=val))
+                found = True
+                break
+
+        # <qemu:commandline> exists but there is no <qemu:arg value global>
+        # so, we add those missing arguments inside the exising cmdline
+        if not found:
+            EM = ElementMaker(namespace=QEMU_NAMESPACE,
+                              nsmap={'qemu': QEMU_NAMESPACE})
+            cmdline.append(EM.arg(value='-global'))
+            cmdline.append(EM.arg(value=val))
+
+        return etree.tostring(root, encoding='utf-8', pretty_print=True)
+
     def _get_scsi_device_xml(self, dev_info):
         adapter = E.adapter(name=('scsi_host%s' % dev_info['host']))
         address = E.address(type='scsi', bus=str(dev_info['bus']),
@@ -285,7 +408,8 @@ class VMHostDevModel(object):
                         'type': e.attrib['type'],
                         'product': dev_info.get('product', None),
                         'vendor': dev_info.get('vendor', None),
-                        'multifunction': dev_info.get('multifunction', None)}
+                        'multifunction': dev_info.get('multifunction', None),
+                        'vga3d': dev_info.get('vga3d', None)}
 
         raise NotFoundError('KCHVMHDEV0001E',
                             {'vmid': vmid, 'dev_name': dev_name})
@@ -304,6 +428,13 @@ class VMHostDevModel(object):
         pci_devs = [(DeviceModel.deduce_dev_name(e, self.conn), e)
                     for e in hostdev if e.attrib['type'] == 'pci']
 
+        dev_model = DeviceModel(conn=self.conn)
+        dev_info = dev_model.lookup(dev_name)
+        is_3D_device = dev_model.is_device_3D_controller(dev_info)
+        if is_3D_device and DOM_STATE_MAP[dom.info()[0]] != "shutoff":
+            raise InvalidOperation('KCHVMHDEV0006E',
+                                   {'name': dev_info['name']})
+
         for e in hostdev:
             if DeviceModel.deduce_dev_name(e, self.conn) == dev_name:
                 xmlstr = etree.tostring(e)
@@ -311,6 +442,8 @@ class VMHostDevModel(object):
                     xmlstr, get_vm_config_flag(dom, mode='all'))
                 if e.attrib['type'] == 'pci':
                     self._delete_affected_pci_devices(dom, dev_name, pci_devs)
+                if is_3D_device:
+                    self.update_mmio_guest(vmid, False)
                 break
         else:
             raise NotFoundError('KCHVMHDEV0001E',
-- 
1.9.1




More information about the Kimchi-devel mailing list