Skip to content

Commit d3cfbf3

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 <dheerajkumar.srivastava@amd.com>
1 parent 8972062 commit d3cfbf3

File tree

3 files changed

+369
-1
lines changed

3 files changed

+369
-1
lines changed

avocado/utils/vfio.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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 <dheerajkumar.srivastava@amd.com> # 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 a vfio container by verifying the api version and ensuring that the
49+
required iommu extension is supported. If either validation fails, an exception
50+
is raised with 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+
try:
120+
ioctl(group_fd, vfio_group_set_container, ctypes.c_void_p(container_fd))
121+
except OSError as e:
122+
raise ValueError(
123+
f"failed to attach pci device's iommu group to the vfio container: {e}"
124+
) from e
125+
126+
127+
def detach_group_from_container(group_fd, container_fd, vfio_group_unset_container):
128+
"""detach the iommu group of pci device from vfio container
129+
130+
:param group_fd: iommu group file descriptor
131+
:type group_fd: int
132+
:param container_fd: vfio container file descriptor
133+
:type container_fd: int
134+
:param vfio_group_unset_container: vfio ioctl to detach iommu group from the
135+
container fd
136+
:type vfio_group_unset_container: int
137+
:raises ValueError: if detaching the group to the container fails.
138+
"""
139+
140+
try:
141+
ioctl(group_fd, vfio_group_unset_container, ctypes.c_void_p(container_fd))
142+
except OSError as e:
143+
raise ValueError(
144+
f"failed to detach pci device's iommu group from vfio container: {e}"
145+
) from e
146+
147+
148+
def get_device_fd(device, group_fd, vfio_group_get_device_fd):
149+
"""Get device file descriptor
150+
151+
:param device: full pci address including domain (0000:03:00.0)
152+
:type device: str
153+
:param group_fd: iommu group file descriptor
154+
:type group_fd: int
155+
:param vfio_group_get_device_fd: ioctl to get device fd
156+
:type vfio_group_get_device_fd: int
157+
:raises ValueError: if not able to get device descriptor
158+
:return: device descriptor
159+
:rtype: int
160+
"""
161+
buf = ctypes.create_string_buffer(device.encode("utf-8") + b"\x00")
162+
try:
163+
device_fd = ioctl(group_fd, vfio_group_get_device_fd, buf)
164+
except OSError as e:
165+
raise ValueError("failed to get vfio device fd") from e
166+
167+
return device_fd
168+
169+
170+
def vfio_device_supports_irq(
171+
device_fd, vfio_pci_msix_irq_index, vfio_device_get_irq_info, count
172+
):
173+
"""Check if device supports at least count number of interrupts
174+
175+
:param device_fd: device file descriptor
176+
:type device_fd: int
177+
:param vfio_pci_msix_irq_index: vfio ioctl to get irq index for msix
178+
:type vfio_pci_msix_irq_index: int
179+
:param vfio_device_get_irq_info: vfio ioctl to get vfio device irq information
180+
:type vfio_device_get_irq_info: int
181+
:param count: number of irqs the device should support
182+
:type count: int
183+
:return: true if supported, false otherwise
184+
:rtype: bool
185+
"""
186+
argsz = struct.calcsize("IIII")
187+
index = vfio_pci_msix_irq_index
188+
irq_info_request = struct.pack("IIII", argsz, 1, index, 1)
189+
irq_info_response = ioctl(device_fd, vfio_device_get_irq_info, irq_info_request)
190+
nirq = (struct.unpack("IIII", irq_info_response))[3]
191+
if nirq < count:
192+
return False
193+
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": 934,
30+
"unit": 948,
3131
"jobs": 11,
3232
"functional-parallel": 353,
3333
"functional-serial": 7,

selftests/unit/utils/vfio.py

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

0 commit comments

Comments
 (0)