pci_devices.py 13.3 KB
Newer Older
1
"""
X
Xu Han 已提交
2
This is a virt-test test for testing PCI devices in various PCI setups
3 4 5 6 7 8

:author: Lukas Doktor <ldoktor@redhat.com>
:copyright: 2013 Red Hat, Inc.
"""
import logging
import random
9
import re
10
import six
11

12
from virttest import env_process
X
Xu Han 已提交
13
from virttest import error_context
14 15
from virttest import qemu_qtree

16 17

class PCIBusInfo:
L
Lucas Meneghel Rodrigues 已提交
18

19 20 21
    """
    Structured info about PCI bus
    """
L
Lucas Meneghel Rodrigues 已提交
22

23 24 25 26 27 28 29 30 31 32 33 34 35
    def __init__(self, device):
        self.name = device.aobject
        if device.child_bus:
            bus = device.child_bus[0]
            self.type = bus.type == 'PCI'
            self.first = bus.first_port[0]
            self.last = bus.addr_lengths[0]
        else:
            self.type = True    # PCI
            self.first = 0      # (first usable)
            self.last = 32      # (last + 1)


36
def process_qdev(qdev):
37 38 39
    """
    Get PCI devices from qemu_devices representation
    """
40 41 42 43
    qdev_devices = {}
    qdev_devices_noid = []
    for bus in qdev.get_buses({'type': ('PCI', 'PCIE')}):
        for device in bus:
44
            if isinstance(device, six.string_types):
45 46 47 48 49 50 51 52 53 54
                logging.error("Not a device %s (bus %s)", device, bus)
                continue
            dev_id = device.get_param('id')
            addr = [int(_, 16) for _ in device.get_param('addr').split('.')]
            if len(addr) == 1:
                addr.append(0)
            addr = "%02x.%x" % (addr[0], addr[1])
            dev = {'id': dev_id,
                   'type': device.get_param('driver'),
                   'bus': device.get_param('bus'),
55
                   'addr': addr}
56 57 58 59
            if dev_id is None:
                qdev_devices_noid.append(dev)
            else:
                qdev_devices[dev_id] = dev
60 61
    return (qdev_devices, qdev_devices_noid)

62

63
def process_qtree(qtree):
64 65 66
    """
    Get PCI devices from qtree
    """
67 68
    qtree_devices = {}
    qtree_devices_noid = []
69
    qtree_pciinfo = []
70 71 72 73 74 75
    for node in qtree.get_nodes():
        if node.parent and node.parent.qtree.get('type') in ('PCI', 'PCIE'):
            dev_id = node.qtree.get('id')
            dev = {'id': dev_id,
                   'type': node.qtree.get('type'),
                   'bus': node.parent.qtree.get('id'),
76
                   'addr': node.qtree.get('addr')}
77 78
            if dev_id is None:
                # HOOK for VGA
79
                if 'vga' in dev['type'].lower():
80 81 82 83
                    dev['type'] = None
                qtree_devices_noid.append(dev)
            else:
                qtree_devices[dev_id] = dev
84 85 86 87 88 89 90 91

            qtree_pciinfo.append({'class_addr': node.qtree.get('class_addr'),
                                  'class_pciid': node.qtree.get('class_pciid')
                                  })
    return (qtree_devices, qtree_devices_noid, qtree_pciinfo)


def process_lspci(lspci):
92 93 94
    """
    Get info about PCI devices from lspci
    """
95 96 97 98 99 100
    lspci = re.findall(r'(\w\w:\w\w.\w) "[^"]+ \[\w{4}\]" "[^"]+ '
                       r'\[(\w{4})\]" "[^"]+ \[(\w{4})\].*', lspci)
    return [{'class_addr': info[0],
             'class_pciid': "%s:%s" % (info[1], info[2])}
            for info in lspci]

101 102

def verify_qdev_vs_qtree(qdev_info, qtree_info):
103 104 105
    """
    Compare qemu_devices and qtree devices
    """
106 107
    qdev_devices, qdev_devices_noid = qdev_info
    qtree_devices, qtree_devices_noid = qtree_info[:2]
108 109

    errors = ""
X
Xu Han 已提交
110
    for dev_id, device in qtree_devices.items():
111 112 113
        if dev_id not in qdev_devices:
            errors += "Device %s is in qtree but not in qdev.\n" % dev_id
            continue
X
Xu Han 已提交
114
        for key, value in device.items():
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
            err = ""
            if qdev_devices[dev_id][key] != value:
                err += "  %s != %s\n" % (qdev_devices[dev_id][key], value)
            if err:
                errors += ("Device %s properties mismatch:\n%s"
                           % (dev_id, err))

    for dev_id in qdev_devices:
        if dev_id not in qtree_devices:
            errors += "Device %s is in qdev but not in qtree\n" % dev_id

    for device in qtree_devices_noid:
        for qdev_device in qdev_devices_noid:
            if qdev_device == device:
                qdev_devices_noid.remove(device)
                break
        else:
            errors += "No match in qdev for device without id %s\n" % device
    for device in qdev_devices_noid:
        errors += "No match in qtree for device without id %s\n" % device

    return errors


139
def verify_lspci(info_lspci, info_qtree):
140 141 142
    """
    Compare lspci and qtree info
    """
143 144 145 146 147 148 149 150 151 152 153 154
    errors = ""
    for lspci_dev in info_lspci:
        if lspci_dev not in info_qtree:
            errors += "Device %s is in lspci but not in qtree\n" % lspci_dev

    for qtree_dev in info_qtree:
        if qtree_dev not in info_lspci:
            errors += "Device %s is in qtree but not in lspci\n" % qtree_dev

    return errors


155
def add_bus(qdev, params, bus_type, name, parent_bus):
156 157 158
    """
    Define new bus in params
    """
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    if bus_type == 'bridge':
        if parent_bus.type is True:    # PCI
            bus_type = 'pci-bridge'
        else:   # PCIE
            bus_type = 'i82801b11-bridge'
    elif bus_type == 'switch':
        bus_type = 'x3130'
    elif bus_type == 'root':
        bus_type = 'ioh3420'
    params['pci_controllers'] += " %s" % name
    params['type_%s' % name] = bus_type
    params['pci_bus_%s' % name] = parent_bus.name
    pci_params = params.object_params(name)
    bus = PCIBusInfo(qdev.pcic_by_params(name, pci_params))
    return params, bus


def add_devices_first(params, name_idxs, bus, add_device):
177 178 179
    """
    Define new device and set it to the first available port
    """
180 181 182 183 184
    params, name_idxs = add_device(params, name_idxs, bus.name, bus.first)
    return params, name_idxs


def add_devices_all(params, name_idxs, bus, add_device):
185 186 187
    """
    Fill all available slots of certain bus with devices
    """
X
Xu Han 已提交
188
    for addr in range(bus.first, bus.last):
189 190 191 192 193
        params, name_idxs = add_device(params, name_idxs, bus.name, addr)
    return params, name_idxs


def add_devices_random(params, name_idxs, bus, add_device):
194 195 196
    """
    Define three devices in first, last and random ports of the given bus
    """
197 198 199 200 201 202 203 204 205
    params, name_idxs = add_device(params, name_idxs, bus.name, bus.first)
    params, name_idxs = add_device(params, name_idxs, bus.name,
                                   random.randrange(bus.first + 1,
                                                    bus.last - 1))
    params, name_idxs = add_device(params, name_idxs, bus.name, bus.last - 1)
    return params, name_idxs


def add_device_usb(params, name_idxs, parent_bus, addr, device):
206 207 208
    """
    Wrapper to add usb device
    """
209 210 211 212 213 214 215 216 217
    idx = name_idxs.get(device[0], 0) + 1
    name_idxs[device[0]] = idx
    name = "test_%s%d" % (device[0], idx)
    params['usbs'] += ' %s' % name
    params['pci_bus_%s' % name] = parent_bus
    params['pci_addr_%s' % name] = addr
    params['usb_type_%s' % name] = device[1]
    if not params.get('reserved_slots_%s' % parent_bus):
        params['reserved_slots_%s' % parent_bus] = ""
218
    params['reserved_slots_%s' % parent_bus] += " 0x%x-0x0" % addr
219 220 221 222 223
    logging.debug("Add test device %s %s %s addr:%s", name, device[1],
                  parent_bus, addr)
    return params, name_idxs


224 225 226 227 228 229 230 231
def add_device_usb_uhci(params, name_idxs, parent_bus, addr):
    """
    Creates ehci usb controller
    """
    return add_device_usb(params, name_idxs, parent_bus,
                          addr, ('uhci', 'ich9-usb-uhci1'))


232
def add_device_usb_ehci(params, name_idxs, parent_bus, addr):
233 234 235
    """
    Creates ehci usb controller
    """
236 237 238 239 240
    return add_device_usb(params, name_idxs, parent_bus,
                          addr, ('ehci', 'usb-ehci'))


def add_device_usb_xhci(params, name_idxs, parent_bus, addr):
241 242 243
    """
    Creates xhci usb controller
    """
244 245 246 247
    return add_device_usb(params, name_idxs, parent_bus,
                          addr, ('xhci', 'nec-usb-xhci'))


248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
def add_virtio_disk(params, name_idxs, parent_bus, addr):
    """
    Creates virtio disks
    """
    idx = name_idxs.get("virtio_disk", 0) + 1
    name_idxs["virtio_disk"] = idx
    name = "test_virtio_disk%d" % idx
    params['images'] += ' %s' % name
    params['image_name_%s' % name] = 'images/%s' % name
    params['pci_bus_%s' % name] = parent_bus
    params['drive_bus_%s' % name] = addr
    params['drive_format_%s' % name] = 'virtio'
    params['force_create_image_%s' % name] = 'yes'
    params['remove_image_%s' % name] = 'yes'
    params['image_size_%s' % name] = '1M'
    if not params.get('reserved_slots_%s' % parent_bus):
        params['reserved_slots_%s' % parent_bus] = ""
265
    params['reserved_slots_%s' % parent_bus] += " 0x%x-0x0" % addr
266 267 268 269 270
    logging.debug("Add test device %s virtio_disk %s addr:%s", name,
                  parent_bus, addr)
    return params, name_idxs


271
def add_device_random(params, name_idxs, parent_bus, addr):
272 273 274
    """
    Add device of random type
    """
275 276
    variants = (add_device_usb_uhci, add_device_usb_ehci, add_device_usb_xhci,
                add_virtio_disk)
277 278 279
    return random.choice(variants)(params, name_idxs, parent_bus, addr)


X
Xu Han 已提交
280
@error_context.context_aware
281
def run(test, params, env):
282
    """
283
    PCI Devices test
284
    1) prints out the setup to be used
285
    2) boots the defined VM
286
    3) verifies monitor "info qtree" vs. internal representation
287 288 289
    4) verifies guest "lspci" vs. info qtree (Linux only)
    :note: Only PCI device properties are checked

290
    :param test: VirtTest instance
291 292 293
    :param params: Dictionary with the test parameters
    :param env: Dictionary with test environment
    """
X
Xu Han 已提交
294
    error_context.context("Creating early names representation")
295 296 297
    env_process.preprocess_vm(test, params, env, params["main_vm"])
    vm = env.get_vm(params["main_vm"])
    qdev = vm.make_create_command()    # parse params into qdev
298 299
    if isinstance(qdev, tuple):
        qdev = qdev[0]
300

X
Xu Han 已提交
301
    error_context.context("Getting main PCI bus info")
302

X
Xu Han 已提交
303
    error_context.context("Processing test params")
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
    test_params = params['test_setup']
    test_devices = params['test_devices']
    test_device_type = params['test_device_type']
    if not params.get('pci_controllers'):
        params['pci_controllers'] = ''
    _lasts = [PCIBusInfo(qdev.get_by_properties({'aobject': 'pci.0'})[0])]
    _lasts[0].first = 7     # first 6 slots might be already occupied on pci.0
    _lasts[0].last -= 1     # last port is usually used by the VM
    use_buses = []
    names = {}
    logging.info("Test setup")
    for line in test_params.split('\\n'):
        _idx = 0
        out = ""
        for device in line.split('->'):
            device = device.strip()
            if device:
                if device == 'devices':
                    use_buses.append(_lasts[_idx])
                    out += "->(test_devices)"
                    break
                idx = names.get(device, 0) + 1
326
                name = "test_pci_%s%d" % (device, idx)
327 328
                names[device] = idx
                params, bus = add_bus(qdev, params, device, name, _lasts[_idx])
329 330
                # we inserted a device, increase the upper bus first idx
                _lasts[_idx].first += 1
331 332 333 334 335 336 337 338 339 340 341 342 343
                out += "->%s" % (name)
                _idx += 1
                if len(_lasts) > _idx:
                    _lasts = _lasts[:_idx]
                _lasts.append(bus)
            else:
                _idx += 1
                out += " " * (len(_lasts[_idx].name) + 2)
        logging.info(out)

    add_devices = {'first': add_devices_first,
                   'all': add_devices_all}.get(test_devices,
                                               add_devices_random)
344 345
    add_device = {'uhci': add_device_usb_uhci,
                  'ehci': add_device_usb_ehci,
346 347 348
                  'xhci': add_device_usb_xhci,
                  'virtio_disk': add_virtio_disk,
                  }.get(test_device_type, add_device_random)
349 350 351 352
    name_idxs = {}
    for bus in use_buses:
        params, name_idxs = add_devices(params, name_idxs, bus, add_device)
    params['start_vm'] = 'yes'
353 354
    env_process.process(test, params, env, env_process.preprocess_image,
                        env_process.preprocess_vm)
355
    vm = env.get_vm(params["main_vm"])
356 357

    # PCI devices are initialized by firmware, which might require some time
358 359 360
    # to setup. Wait until the guest boots up.
    error_context.context("Verify VM booted properly.", logging.info)
    session = vm.wait_for_login()
361

X
Xu Han 已提交
362
    error_context.context("Verify qtree vs. qemu devices", logging.info)
363
    qtree = qemu_qtree.QtreeContainer()
364 365 366 367 368 369 370 371
    _info_qtree = vm.monitor.info('qtree', False)
    qtree.parse_info_qtree(_info_qtree)
    info_qdev = process_qdev(vm.devices)
    info_qtree = process_qtree(qtree)
    errors = ""
    err = verify_qdev_vs_qtree(info_qdev, info_qtree)
    if err:
        logging.error(_info_qtree)
372
        logging.error(qtree.get_qtree().str_qtree())
373
        logging.error(vm.devices.str_bus_long())
374 375
        logging.error(err)
        errors += "qdev vs. qtree, "
376

X
Xu Han 已提交
377
    error_context.context("Verify lspci vs. qtree", logging.info)
378 379 380 381 382 383 384 385 386 387
    if params.get('lspci_cmd'):
        _info_lspci = session.cmd_output(params['lspci_cmd'])
        info_lspci = process_lspci(_info_lspci)
        err = verify_lspci(info_lspci, info_qtree[2])
        if err:
            logging.error(_info_lspci)
            logging.error(_info_qtree)
            logging.error(err)
            errors += "qtree vs. lspci, "

X
Xu Han 已提交
388
    error_context.context("Results")
389
    if errors:
X
Xu Han 已提交
390 391
        test.fail("Errors occurred while comparing %s. Please check"
                  " the log for details." % errors[:-2])