Skip to content

Commit 32dfd3d

Browse files
committed
utils/vfio: Add APIs for VFIO container and device management
This patch introduces a set of VFIO (Virtual Function I/O) helper APIs to simplify container, IOMMU group, and device management. The new functions provide a clean abstraction over common VFIO operations: * get_vfio_container_fd() – open and validate a VFIO container * check_vfio_container() – verify API version and IOMMU extension support * get_iommu_group_fd() – obtain an IOMMU group file descriptor * attach_group_to_container()/detach_group_from_container() – manage group association with a VFIO container * get_device_fd() – retrieve a VFIO device file descriptor * vfio_device_supports_irq() – check if a device supports the requested number of MSI-X interrupts Signed-off-by: Dheeraj Kumar Srivastava <[email protected]>
1 parent b6ac559 commit 32dfd3d

File tree

3 files changed

+371
-1
lines changed

3 files changed

+371
-1
lines changed

avocado/utils/vfio.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# This program is free software; you can redistribute it and/or modify
2+
# it under the terms of the GNU General Public License as published by
3+
# the Free Software Foundation; either version 2 of the License, or
4+
# (at your option) any later version.
5+
#
6+
# This program is distributed in the hope that it will be useful,
7+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
8+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9+
#
10+
# See LICENSE for more details.
11+
#
12+
# Copyright: 2025 Advanced Micro Devices, Inc.
13+
# Author: Dheeraj Kumar Srivastava <[email protected]> # pylint: disable=C0401
14+
15+
# pylint: disable=C0402
16+
17+
"""APIs for virtual function I/O management."""
18+
19+
20+
import ctypes
21+
import os
22+
import struct
23+
from fcntl import ioctl
24+
25+
from avocado.utils import pci
26+
27+
28+
def get_vfio_container_fd():
29+
"""get vfio container file descriptor
30+
31+
:return: file descriptor of the vfio container.
32+
:rtype: int
33+
:raises ValueError: if the vfio container cannot be opened.
34+
"""
35+
try:
36+
return os.open("/dev/vfio/vfio", os.O_RDWR | os.O_CLOEXEC)
37+
except OSError as e:
38+
raise ValueError(f"Failed to open VFIO container: {e}") from e
39+
40+
41+
def check_vfio_container(
42+
container_fd,
43+
vfio_get_api_version,
44+
vfio_api_version,
45+
vfio_check_extension,
46+
vfio_type_iommu,
47+
):
48+
"""validate the vfio container by verifying the api version and ensuring the required
49+
iommu extension is supported. if either validation fails, the test is canceled with
50+
an appropriate message.
51+
52+
:param container_fd: vfio container file descriptor
53+
:type container_fd: int
54+
:param vfio_get_api_version: ioctl to retrieve the vfio container api version
55+
:type vfio_get_api_version: int
56+
:param vfio_api_version: expected vfio api version
57+
:type vfio_api_version: int
58+
:param vfio_check_extension: ioctl to check iommu extension support
59+
:type vfio_check_extension: int
60+
:param vfio_type_iommu: expected vfio iommu type
61+
:type vfio_type_iommu: int
62+
:raises ValueError: if the vfio api version is invalid or if the required
63+
iommu extension is not supported.
64+
:return: True if vfio container fd check passes.
65+
:rtype: bool
66+
"""
67+
if ioctl(container_fd, vfio_get_api_version) != vfio_api_version:
68+
raise ValueError("Failed to get right API version")
69+
70+
if not ioctl(container_fd, vfio_check_extension, vfio_type_iommu):
71+
raise ValueError("does not support type 1 iommu")
72+
73+
return True
74+
75+
76+
def get_iommu_group_fd(device, vfio_group_get_status, vfio_group_flags_viable):
77+
"""get iommu group fd for the pci device
78+
79+
:param device: full pci address including domain (0000:03:00.0)
80+
:type device: str
81+
:param vfio_group_get_status: ioctl to get iommu group status
82+
:type vfio_group_get_status: int
83+
:param vfio_group_flags_viable: ioctl to check if iommu group is viable
84+
:type vfio_group_flags_viable: int
85+
:raises ValueError: if the vfio group device cannot be opened or the group
86+
is not viable.
87+
:return: file descriptor for the iommu group.
88+
:rtype: int
89+
"""
90+
vfio_group = f"/dev/vfio/{pci.get_iommu_group(device)}"
91+
try:
92+
group_fd = os.open(vfio_group, os.O_RDWR)
93+
except OSError as e:
94+
raise ValueError(f"Failed to open {vfio_group}: {e}") from e
95+
96+
argsz = struct.calcsize("II")
97+
group_status_request = struct.pack("II", argsz, 2)
98+
group_status_response = ioctl(group_fd, vfio_group_get_status, group_status_request)
99+
group_status = struct.unpack("II", group_status_response)
100+
101+
if not group_status[1] & vfio_group_flags_viable:
102+
raise ValueError("Group not viable, are all devices attached to vfio?")
103+
104+
return group_fd
105+
106+
107+
def attach_group_to_container(group_fd, container_fd, vfio_group_set_container):
108+
"""attach the iommu group of pci device to the vfio container.
109+
110+
:param group_fd: iommu group file descriptor
111+
:type group_fd: int
112+
:param container_fd: vfio container file descriptor
113+
:type container_fd: int
114+
:param vfio_group_set_container: vfio ioctl to add iommu group to the container fd
115+
:type vfio_group_set_container: int
116+
:raises ValueError: if attaching the group to the container fails.
117+
"""
118+
119+
ret = ioctl(group_fd, vfio_group_set_container, ctypes.c_void_p(container_fd))
120+
if ret:
121+
raise ValueError(
122+
"failed to attached pci device's iommu group to the vfio container"
123+
)
124+
125+
126+
def detach_group_from_container(group_fd, container_fd, vfio_group_unset_container):
127+
"""detach the iommu group of pci device from vfio container
128+
129+
:param group_fd: iommu group file descriptor
130+
:type group_fd: int
131+
:param container_fd: vfio container file descriptor
132+
:type container_fd: int
133+
:param vfio_group_unset_container: vfio ioctl to detach iommu group from the
134+
container fd
135+
:type vfio_group_unset_container: int
136+
:raises ValueError: if detaching the group to the container fails.
137+
"""
138+
ret = ioctl(group_fd, vfio_group_unset_container, ctypes.c_void_p(container_fd))
139+
if ret:
140+
raise ValueError(
141+
"failed to detach pci device's iommu group from vfio container"
142+
)
143+
144+
145+
def get_device_fd(device, group_fd, vfio_group_get_device_fd):
146+
"""Get device file descriptor
147+
148+
:param device: full pci address including domain (0000:03:00.0)
149+
:type device: str
150+
:param group_fd: iommu group file descriptor
151+
:type group_fd: int
152+
:param vfio_group_get_device_fd: ioctl to get device fd
153+
:type vfio_group_get_device_fd: int
154+
:raises ValueError: if not able to get device descriptor
155+
:return: device descriptor
156+
:rtype: int
157+
"""
158+
buf = ctypes.create_string_buffer(device.encode("utf-8") + b"\x00")
159+
try:
160+
device_fd = ioctl(group_fd, vfio_group_get_device_fd, buf)
161+
except OSError as e:
162+
raise ValueError("failed to get vfio device fd") from e
163+
164+
return device_fd
165+
166+
167+
def vfio_device_supports_irq(
168+
device_fd, vfio_pci_msix_irq_index, vfio_device_get_irq_info, count
169+
):
170+
"""Check if device supports at least count number of interrupts
171+
172+
:param device_fd: device file descriptor
173+
:type device_fd: int
174+
:param vfio_pci_msix_irq_index: vfio ioctl to get irq index for msix
175+
:type vfio_pci_msix_irq_index: int
176+
:param vfio_device_get_irq_info: vfio ioctl to get vfio device irq information
177+
:type vfio_device_get_irq_info: int
178+
:param count: number of irqs the device should support
179+
:type count: int
180+
:return: true if supported, false otherwise
181+
:rtype: bool
182+
"""
183+
argsz = struct.calcsize("IIII")
184+
index = vfio_pci_msix_irq_index
185+
irq_info_request = struct.pack("IIII", argsz, 1, index, 1)
186+
irq_info_response = ioctl(device_fd, vfio_device_get_irq_info, irq_info_request)
187+
nirq = (struct.unpack("IIII", irq_info_response))[3]
188+
if nirq < count:
189+
return False
190+
return True

selftests/check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"job-api-check-tmp-directory-exists": 1,
2828
"nrunner-interface": 90,
2929
"nrunner-requirement": 28,
30-
"unit": 874,
30+
"unit": 889,
3131
"jobs": 11,
3232
"functional-parallel": 342,
3333
"functional-serial": 7,

selftests/unit/utils/vfio.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import os
2+
import struct
3+
import unittest
4+
from unittest import mock
5+
6+
from avocado import Test
7+
from avocado.utils import vfio
8+
9+
10+
class VfioUtilsTests(Test):
11+
@mock.patch("os.open")
12+
def test_get_vfio_container_fd_success(self, mock_open):
13+
mock_open.return_value = 10
14+
vfio.get_vfio_container_fd()
15+
16+
@mock.patch("os.open")
17+
def test_get_vfio_container_fd_fail(self, mock_open):
18+
mock_open.side_effect = OSError("No such file or directory")
19+
with self.assertRaises(ValueError) as e:
20+
vfio.get_vfio_container_fd()
21+
self.assertIn("Failed to open VFIO container", str(e.exception))
22+
23+
@mock.patch("avocado.utils.vfio.ioctl")
24+
def test_invalid_api_version(self, mock_ioctl):
25+
mock_ioctl.side_effect = [1, True]
26+
with self.assertRaises(ValueError) as e:
27+
vfio.check_vfio_container(
28+
container_fd=3,
29+
vfio_get_api_version=15204,
30+
vfio_api_version=0,
31+
vfio_check_extension=15205,
32+
vfio_type_iommu=1,
33+
)
34+
self.assertIn("Failed to get right API version", str(e.exception))
35+
36+
@mock.patch("avocado.utils.vfio.ioctl")
37+
def test_missing_iommu_extension(self, mock_ioctl):
38+
mock_ioctl.side_effect = [0, False]
39+
with self.assertRaises(ValueError) as e:
40+
vfio.check_vfio_container(
41+
container_fd=3,
42+
vfio_get_api_version=15204,
43+
vfio_api_version=0,
44+
vfio_check_extension=15205,
45+
vfio_type_iommu=1,
46+
)
47+
self.assertIn("does not support type 1 iommu", str(e.exception))
48+
49+
@mock.patch("avocado.utils.vfio.os.open")
50+
@mock.patch("avocado.utils.vfio.pci.get_iommu_group")
51+
def test_get_group_fd_fail_1(self, mock_get_group, mock_open):
52+
mock_open.side_effect = OSError("No such file or directory")
53+
mock_get_group.return_value = "42"
54+
with self.assertRaises(ValueError) as e:
55+
vfio.get_iommu_group_fd(
56+
device="0000:03:00.0",
57+
vfio_group_get_status=100,
58+
vfio_group_flags_viable=0x1,
59+
)
60+
self.assertIn("Failed to open /dev/vfio/42", str(e.exception))
61+
62+
@mock.patch("avocado.utils.vfio.ioctl")
63+
@mock.patch("avocado.utils.vfio.os.open")
64+
@mock.patch("avocado.utils.vfio.pci.get_iommu_group")
65+
def test_get_group_fd_fail_2(self, mock_get_group, mock_open, mock_ioctl):
66+
mock_open.return_value = 3
67+
mock_get_group.return_value = "42"
68+
argsz = struct.calcsize("II")
69+
# Pack request, ioctl returns same but with flags = 0
70+
mock_ioctl.return_value = struct.pack("II", argsz, 0)
71+
72+
with self.assertRaises(ValueError) as ctx:
73+
vfio.get_iommu_group_fd(
74+
device="0000:03:00.0",
75+
vfio_group_get_status=100,
76+
vfio_group_flags_viable=0x1,
77+
)
78+
self.assertIn("Group not viable", str(ctx.exception))
79+
80+
@mock.patch("avocado.utils.vfio.ioctl")
81+
@mock.patch("avocado.utils.vfio.os.open")
82+
@mock.patch("avocado.utils.vfio.pci.get_iommu_group")
83+
def test_get_group_fd_success(self, mock_get_group, mock_open, mock_ioctl):
84+
mock_open.return_value = 3
85+
mock_get_group.return_value = "42"
86+
argsz = struct.calcsize("II")
87+
# Pack request, ioctl returns same but with flags = 1
88+
mock_ioctl.return_value = struct.pack("II", argsz, 0x1)
89+
90+
fd = vfio.get_iommu_group_fd(
91+
device="0000:03:00.0",
92+
vfio_group_get_status=100,
93+
vfio_group_flags_viable=0x1,
94+
)
95+
96+
self.assertEqual(fd, 3)
97+
mock_open.assert_called_once_with("/dev/vfio/42", os.O_RDWR)
98+
mock_ioctl.assert_called_once()
99+
100+
@mock.patch("avocado.utils.vfio.ioctl")
101+
def test_attach_group_to_container_success(self, mock_ioctl):
102+
mock_ioctl.return_value = 0
103+
# Should not raise
104+
vfio.attach_group_to_container(
105+
group_fd=10, container_fd=20, vfio_group_set_container=100
106+
)
107+
108+
@mock.patch("avocado.utils.vfio.ioctl")
109+
def test_attach_group_to_container_failure(self, mock_ioctl):
110+
mock_ioctl.return_value = 1
111+
with self.assertRaises(ValueError) as ctx:
112+
vfio.attach_group_to_container(
113+
group_fd=10, container_fd=20, vfio_group_set_container=100
114+
)
115+
self.assertIn("failed to attached pci device", str(ctx.exception))
116+
117+
@mock.patch("avocado.utils.vfio.ioctl")
118+
def test_detach_group_from_container_success(self, mock_ioctl):
119+
mock_ioctl.return_value = 0
120+
vfio.detach_group_from_container(
121+
group_fd=10, container_fd=20, vfio_group_unset_container=200
122+
)
123+
124+
@mock.patch("avocado.utils.vfio.ioctl")
125+
def test_detach_group_from_container_failure(self, mock_ioctl):
126+
mock_ioctl.return_value = 1
127+
with self.assertRaises(ValueError) as ctx:
128+
vfio.detach_group_from_container(
129+
group_fd=10, container_fd=20, vfio_group_unset_container=200
130+
)
131+
self.assertIn("failed to detach pci device", str(ctx.exception))
132+
133+
@mock.patch("avocado.utils.vfio.ioctl")
134+
def test_get_device_fd_success(self, mock_ioctl):
135+
mock_ioctl.return_value = 50
136+
device_fd = vfio.get_device_fd(
137+
"0000:03:00.0", group_fd=10, vfio_group_get_device_fd=200
138+
)
139+
self.assertEqual(device_fd, 50)
140+
141+
@mock.patch("avocado.utils.vfio.ioctl")
142+
def test_get_device_fd_failure(self, mock_ioctl):
143+
mock_ioctl.side_effect = OSError("Device not found")
144+
with self.assertRaises(ValueError) as e:
145+
vfio.get_device_fd(
146+
"0000:03:00.0", group_fd=10, vfio_group_get_device_fd=200
147+
)
148+
self.assertIn("failed to get vfio device fd", str(e.exception))
149+
150+
@mock.patch("avocado.utils.vfio.ioctl")
151+
def test_vfio_device_supports_irq_success(self, mock_ioctl):
152+
argsz = struct.calcsize("IIII")
153+
response = struct.pack("IIII", argsz, 0, 0, 8)
154+
mock_ioctl.return_value = response
155+
156+
result = vfio.vfio_device_supports_irq(
157+
device_fd=30,
158+
vfio_pci_msix_irq_index=100,
159+
vfio_device_get_irq_info=300,
160+
count=4,
161+
)
162+
self.assertTrue(result)
163+
164+
@mock.patch("avocado.utils.vfio.ioctl")
165+
def test_vfio_device_supports_irq_fail(self, mock_ioctl):
166+
argsz = struct.calcsize("IIII")
167+
response = struct.pack("IIII", argsz, 0, 0, 2)
168+
mock_ioctl.return_value = response
169+
170+
result = vfio.vfio_device_supports_irq(
171+
device_fd=30,
172+
vfio_pci_msix_irq_index=100,
173+
vfio_device_get_irq_info=300,
174+
count=4,
175+
)
176+
self.assertFalse(result)
177+
178+
179+
if __name__ == "__main__":
180+
unittest.main()

0 commit comments

Comments
 (0)