mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
7.3 KiB
Python
202 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2025 Nutanix, Inc.
|
|
#
|
|
# Author:
|
|
# Mark Cave-Ayland <mark.caveayland@nutanix.com>
|
|
# John Levon <john.levon@nutanix.com>
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
"""
|
|
Check basic vfio-user-pci client functionality. The test starts two VMs:
|
|
|
|
- the server VM runs the libvfio-user "gpio" example server inside it,
|
|
piping vfio-user traffic between a local UNIX socket and a virtio-serial
|
|
port. On the host, the virtio-serial port is backed by a local socket.
|
|
|
|
- the client VM loads the gpio-pci-idio-16 kernel module, with the
|
|
vfio-user client connecting to the above local UNIX socket.
|
|
|
|
This way, we don't depend on trying to run a vfio-user server on the host
|
|
itself.
|
|
|
|
Once both VMs are running, we run some basic configuration on the gpio device
|
|
and verify that the server is logging the expected out. As this is consistent
|
|
given the same VM images, we just do a simple direct comparison.
|
|
"""
|
|
|
|
import os
|
|
|
|
from qemu_test import Asset
|
|
from qemu_test import QemuSystemTest
|
|
from qemu_test import exec_command_and_wait_for_pattern
|
|
from qemu_test import wait_for_console_pattern
|
|
|
|
# Exact output can vary, so we just sample for some expected lines.
|
|
EXPECTED_SERVER_LINES = [
|
|
"gpio: adding DMA region [0, 0xc0000) offset=0 flags=0x3",
|
|
"gpio: devinfo flags 0x3, num_regions 9, num_irqs 5",
|
|
"gpio: region_info[0] offset 0 flags 0 size 0 argsz 32",
|
|
"gpio: region_info[1] offset 0 flags 0 size 0 argsz 32",
|
|
"gpio: region_info[2] offset 0 flags 0x3 size 256 argsz 32",
|
|
"gpio: region_info[3] offset 0 flags 0 size 0 argsz 32",
|
|
"gpio: region_info[4] offset 0 flags 0 size 0 argsz 32",
|
|
"gpio: region_info[5] offset 0 flags 0 size 0 argsz 32",
|
|
"gpio: region_info[7] offset 0 flags 0x3 size 256 argsz 32",
|
|
"gpio: region7: read 256 bytes at 0",
|
|
"gpio: region7: read 0 from (0x30:4)",
|
|
"gpio: cleared EROM",
|
|
"gpio: I/O space enabled",
|
|
"gpio: memory space enabled",
|
|
"gpio: SERR# enabled",
|
|
"gpio: region7: wrote 0x103 to (0x4:2)",
|
|
"gpio: I/O space enabled",
|
|
"gpio: memory space enabled",
|
|
]
|
|
|
|
class VfioUserClient(QemuSystemTest):
|
|
"""vfio-user testing class."""
|
|
|
|
ASSET_REPO = 'https://github.com/mcayland-ntx/libvfio-user-test'
|
|
|
|
ASSET_KERNEL = Asset(
|
|
f'{ASSET_REPO}/raw/refs/heads/main/images/bzImage',
|
|
'40292fa6ce95d516e26bccf5974e138d0db65a6de0bc540cabae060fe9dea605'
|
|
)
|
|
|
|
ASSET_ROOTFS = Asset(
|
|
f'{ASSET_REPO}/raw/refs/heads/main/images/rootfs.ext2',
|
|
'e1e3abae8aebb8e6e77f08b1c531caeacf46250c94c815655c6bbea59fc3d1c1'
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.kernel_path = None
|
|
self.rootfs_path = None
|
|
|
|
def configure_server_vm_args(self, server_vm, sock_path):
|
|
"""
|
|
Configuration for the server VM. Set up virtio-serial device backed by
|
|
the given socket path.
|
|
"""
|
|
server_vm.add_args('-kernel', self.kernel_path)
|
|
server_vm.add_args('-append', 'console=ttyS0 root=/dev/sda')
|
|
server_vm.add_args('-drive',
|
|
f"file={self.rootfs_path},if=ide,format=raw,id=drv0")
|
|
server_vm.add_args('-snapshot')
|
|
server_vm.add_args('-chardev',
|
|
f"socket,id=sock0,path={sock_path},telnet=off,server=on,wait=off")
|
|
server_vm.add_args('-device', 'virtio-serial')
|
|
server_vm.add_args('-device',
|
|
'virtserialport,chardev=sock0,name=org.fedoraproject.port.0')
|
|
|
|
def configure_client_vm_args(self, client_vm, sock_path):
|
|
"""
|
|
Configuration for the client VM. Point the vfio-user-pci device to the
|
|
socket path configured above.
|
|
"""
|
|
|
|
client_vm.add_args('-kernel', self.kernel_path)
|
|
client_vm.add_args('-append', 'console=ttyS0 root=/dev/sda')
|
|
client_vm.add_args('-drive',
|
|
f'file={self.rootfs_path},if=ide,format=raw,id=drv0')
|
|
client_vm.add_args('-snapshot')
|
|
client_vm.add_args('-device',
|
|
'{"driver":"vfio-user-pci",' +
|
|
'"socket":{"path": "%s", "type": "unix"}}' % sock_path)
|
|
|
|
def setup_vfio_user_pci_server(self, server_vm):
|
|
"""
|
|
Start the libvfio-user server within the server VM, and arrange
|
|
for data to shuttle between its socket and the virtio serial port.
|
|
"""
|
|
wait_for_console_pattern(self, 'login:', None, server_vm)
|
|
exec_command_and_wait_for_pattern(self, 'root', '#', None, server_vm)
|
|
|
|
exec_command_and_wait_for_pattern(self,
|
|
'gpio-pci-idio-16 -v /tmp/vfio-user.sock >/var/tmp/gpio.out 2>&1 &',
|
|
'#', None, server_vm)
|
|
|
|
# wait for libvfio-user socket to appear
|
|
while True:
|
|
out = exec_command_and_wait_for_pattern(self,
|
|
'ls --color=no /tmp/vfio-user.sock', '#', None, server_vm)
|
|
ls_out = out.decode().splitlines()[1].strip()
|
|
if ls_out == "/tmp/vfio-user.sock":
|
|
break
|
|
|
|
exec_command_and_wait_for_pattern(self,
|
|
'socat UNIX-CONNECT:/tmp/vfio-user.sock /dev/vport0p1,ignoreeof ' +
|
|
' &', '#', None, server_vm)
|
|
|
|
def test_vfio_user_pci(self):
|
|
"""Run basic sanity test."""
|
|
|
|
self.set_machine('pc')
|
|
self.require_device('virtio-serial')
|
|
self.require_device('vfio-user-pci')
|
|
|
|
self.kernel_path = self.ASSET_KERNEL.fetch()
|
|
self.rootfs_path = self.ASSET_ROOTFS.fetch()
|
|
|
|
sock_dir = self.socket_dir()
|
|
socket_path = os.path.join(sock_dir.name, 'vfio-user.sock')
|
|
|
|
server_vm = self.get_vm(name='server')
|
|
server_vm.set_console()
|
|
self.configure_server_vm_args(server_vm, socket_path)
|
|
|
|
server_vm.launch()
|
|
|
|
self.log.debug('starting libvfio-user server')
|
|
|
|
self.setup_vfio_user_pci_server(server_vm)
|
|
|
|
client_vm = self.get_vm(name="client")
|
|
client_vm.set_console()
|
|
self.configure_client_vm_args(client_vm, socket_path)
|
|
|
|
try:
|
|
client_vm.launch()
|
|
except:
|
|
self.log.error('client VM failed to start, dumping server logs')
|
|
exec_command_and_wait_for_pattern(self, 'cat /var/tmp/gpio.out',
|
|
'#', None, server_vm)
|
|
raise
|
|
|
|
self.log.debug('waiting for client VM boot')
|
|
|
|
wait_for_console_pattern(self, 'login:', None, client_vm)
|
|
exec_command_and_wait_for_pattern(self, 'root', '#', None, client_vm)
|
|
|
|
#
|
|
# Here, we'd like to actually interact with the gpio device a little
|
|
# more as described at:
|
|
#
|
|
# https://github.com/nutanix/libvfio-user/blob/master/docs/qemu.md
|
|
#
|
|
# Unfortunately, the buildroot Linux kernel has some undiagnosed issue
|
|
# so we don't get /sys/class/gpio. Nonetheless just the basic
|
|
# initialization and setup is enough for basic testing of vfio-user.
|
|
#
|
|
|
|
self.log.debug('collecting libvfio-user server output')
|
|
|
|
out = exec_command_and_wait_for_pattern(self,
|
|
'cat /var/tmp/gpio.out',
|
|
'gpio: region2: wrote 0 to (0x1:1)',
|
|
None, server_vm)
|
|
|
|
gpio_server_out = [s for s in out.decode().splitlines()
|
|
if s.startswith("gpio:")]
|
|
|
|
for line in EXPECTED_SERVER_LINES:
|
|
if line not in gpio_server_out:
|
|
self.log.error(f'Missing server debug line: {line}')
|
|
self.fail(False)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
QemuSystemTest.main()
|