master
py 201 lines 7.33 KB
Raw
1 #!/usr/bin/env python3
2 #
3 # Copyright (c) 2025 Nutanix, Inc.
4 #
5 # Author:
6 # Mark Cave-Ayland <mark.caveayland@nutanix.com>
7 # John Levon <john.levon@nutanix.com>
8 #
9 # SPDX-License-Identifier: GPL-2.0-or-later
10 """
11 Check basic vfio-user-pci client functionality. The test starts two VMs:
12
13 - the server VM runs the libvfio-user "gpio" example server inside it,
14 piping vfio-user traffic between a local UNIX socket and a virtio-serial
15 port. On the host, the virtio-serial port is backed by a local socket.
16
17 - the client VM loads the gpio-pci-idio-16 kernel module, with the
18 vfio-user client connecting to the above local UNIX socket.
19
20 This way, we don't depend on trying to run a vfio-user server on the host
21 itself.
22
23 Once both VMs are running, we run some basic configuration on the gpio device
24 and verify that the server is logging the expected out. As this is consistent
25 given the same VM images, we just do a simple direct comparison.
26 """
27
28 import os
29
30 from qemu_test import Asset
31 from qemu_test import QemuSystemTest
32 from qemu_test import exec_command_and_wait_for_pattern
33 from qemu_test import wait_for_console_pattern
34
35 # Exact output can vary, so we just sample for some expected lines.
36 EXPECTED_SERVER_LINES = [
37 "gpio: adding DMA region [0, 0xc0000) offset=0 flags=0x3",
38 "gpio: devinfo flags 0x3, num_regions 9, num_irqs 5",
39 "gpio: region_info[0] offset 0 flags 0 size 0 argsz 32",
40 "gpio: region_info[1] offset 0 flags 0 size 0 argsz 32",
41 "gpio: region_info[2] offset 0 flags 0x3 size 256 argsz 32",
42 "gpio: region_info[3] offset 0 flags 0 size 0 argsz 32",
43 "gpio: region_info[4] offset 0 flags 0 size 0 argsz 32",
44 "gpio: region_info[5] offset 0 flags 0 size 0 argsz 32",
45 "gpio: region_info[7] offset 0 flags 0x3 size 256 argsz 32",
46 "gpio: region7: read 256 bytes at 0",
47 "gpio: region7: read 0 from (0x30:4)",
48 "gpio: cleared EROM",
49 "gpio: I/O space enabled",
50 "gpio: memory space enabled",
51 "gpio: SERR# enabled",
52 "gpio: region7: wrote 0x103 to (0x4:2)",
53 "gpio: I/O space enabled",
54 "gpio: memory space enabled",
55 ]
56
57 class VfioUserClient(QemuSystemTest):
58 """vfio-user testing class."""
59
60 ASSET_REPO = 'https://github.com/mcayland-ntx/libvfio-user-test'
61
62 ASSET_KERNEL = Asset(
63 f'{ASSET_REPO}/raw/refs/heads/main/images/bzImage',
64 '40292fa6ce95d516e26bccf5974e138d0db65a6de0bc540cabae060fe9dea605'
65 )
66
67 ASSET_ROOTFS = Asset(
68 f'{ASSET_REPO}/raw/refs/heads/main/images/rootfs.ext2',
69 'e1e3abae8aebb8e6e77f08b1c531caeacf46250c94c815655c6bbea59fc3d1c1'
70 )
71
72 def __init__(self, *args, **kwargs):
73 super().__init__(*args, **kwargs)
74
75 self.kernel_path = None
76 self.rootfs_path = None
77
78 def configure_server_vm_args(self, server_vm, sock_path):
79 """
80 Configuration for the server VM. Set up virtio-serial device backed by
81 the given socket path.
82 """
83 server_vm.add_args('-kernel', self.kernel_path)
84 server_vm.add_args('-append', 'console=ttyS0 root=/dev/sda')
85 server_vm.add_args('-drive',
86 f"file={self.rootfs_path},if=ide,format=raw,id=drv0")
87 server_vm.add_args('-snapshot')
88 server_vm.add_args('-chardev',
89 f"socket,id=sock0,path={sock_path},telnet=off,server=on,wait=off")
90 server_vm.add_args('-device', 'virtio-serial')
91 server_vm.add_args('-device',
92 'virtserialport,chardev=sock0,name=org.fedoraproject.port.0')
93
94 def configure_client_vm_args(self, client_vm, sock_path):
95 """
96 Configuration for the client VM. Point the vfio-user-pci device to the
97 socket path configured above.
98 """
99
100 client_vm.add_args('-kernel', self.kernel_path)
101 client_vm.add_args('-append', 'console=ttyS0 root=/dev/sda')
102 client_vm.add_args('-drive',
103 f'file={self.rootfs_path},if=ide,format=raw,id=drv0')
104 client_vm.add_args('-snapshot')
105 client_vm.add_args('-device',
106 '{"driver":"vfio-user-pci",' +
107 '"socket":{"path": "%s", "type": "unix"}}' % sock_path)
108
109 def setup_vfio_user_pci_server(self, server_vm):
110 """
111 Start the libvfio-user server within the server VM, and arrange
112 for data to shuttle between its socket and the virtio serial port.
113 """
114 wait_for_console_pattern(self, 'login:', None, server_vm)
115 exec_command_and_wait_for_pattern(self, 'root', '#', None, server_vm)
116
117 exec_command_and_wait_for_pattern(self,
118 'gpio-pci-idio-16 -v /tmp/vfio-user.sock >/var/tmp/gpio.out 2>&1 &',
119 '#', None, server_vm)
120
121 # wait for libvfio-user socket to appear
122 while True:
123 out = exec_command_and_wait_for_pattern(self,
124 'ls --color=no /tmp/vfio-user.sock', '#', None, server_vm)
125 ls_out = out.decode().splitlines()[1].strip()
126 if ls_out == "/tmp/vfio-user.sock":
127 break
128
129 exec_command_and_wait_for_pattern(self,
130 'socat UNIX-CONNECT:/tmp/vfio-user.sock /dev/vport0p1,ignoreeof ' +
131 ' &', '#', None, server_vm)
132
133 def test_vfio_user_pci(self):
134 """Run basic sanity test."""
135
136 self.set_machine('pc')
137 self.require_device('virtio-serial')
138 self.require_device('vfio-user-pci')
139
140 self.kernel_path = self.ASSET_KERNEL.fetch()
141 self.rootfs_path = self.ASSET_ROOTFS.fetch()
142
143 sock_dir = self.socket_dir()
144 socket_path = os.path.join(sock_dir.name, 'vfio-user.sock')
145
146 server_vm = self.get_vm(name='server')
147 server_vm.set_console()
148 self.configure_server_vm_args(server_vm, socket_path)
149
150 server_vm.launch()
151
152 self.log.debug('starting libvfio-user server')
153
154 self.setup_vfio_user_pci_server(server_vm)
155
156 client_vm = self.get_vm(name="client")
157 client_vm.set_console()
158 self.configure_client_vm_args(client_vm, socket_path)
159
160 try:
161 client_vm.launch()
162 except:
163 self.log.error('client VM failed to start, dumping server logs')
164 exec_command_and_wait_for_pattern(self, 'cat /var/tmp/gpio.out',
165 '#', None, server_vm)
166 raise
167
168 self.log.debug('waiting for client VM boot')
169
170 wait_for_console_pattern(self, 'login:', None, client_vm)
171 exec_command_and_wait_for_pattern(self, 'root', '#', None, client_vm)
172
173 #
174 # Here, we'd like to actually interact with the gpio device a little
175 # more as described at:
176 #
177 # https://github.com/nutanix/libvfio-user/blob/master/docs/qemu.md
178 #
179 # Unfortunately, the buildroot Linux kernel has some undiagnosed issue
180 # so we don't get /sys/class/gpio. Nonetheless just the basic
181 # initialization and setup is enough for basic testing of vfio-user.
182 #
183
184 self.log.debug('collecting libvfio-user server output')
185
186 out = exec_command_and_wait_for_pattern(self,
187 'cat /var/tmp/gpio.out',
188 'gpio: region2: wrote 0 to (0x1:1)',
189 None, server_vm)
190
191 gpio_server_out = [s for s in out.decode().splitlines()
192 if s.startswith("gpio:")]
193
194 for line in EXPECTED_SERVER_LINES:
195 if line not in gpio_server_out:
196 self.log.error(f'Missing server debug line: {line}')
197 self.fail(False)
198
199
200 if __name__ == '__main__':
201 QemuSystemTest.main()