Skip to content

Commit d7fe806

Browse files
committed
qvm-pci: optionally list SBDF of listed devices
SBDF aka resolved path is what most interfaces use, including lspci, sysfs etc. Include that in listing too, to ease user's life. Note that 'qvm-pci info' already includes that info. QubesOS/qubes-issues#8681
1 parent e6034fd commit d7fe806

File tree

3 files changed

+81
-12
lines changed

3 files changed

+81
-12
lines changed

doc/manpages/qvm-device.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ List devices.
5353

5454
Include info about device assignments, indicated by '*' before qube name.
5555

56+
.. option:: --with-sbdf, --resolve-paths
57+
58+
For PCI devices list also resolved device path (SBDF). This eases looking up the device with other tools like lspci.
59+
The option is ignored when listing non-PCI devices.
60+
5661
.. option:: --all
5762

5863
List devices from all qubes. You can use :option:`--exclude` to limit the

qubesadmin/tests/tools/qvm_device.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@
3535
class TC_00_qvm_device(qubesadmin.tests.QubesTestCase):
3636
""" Tests the output logic of the qvm-device tool """
3737

38-
def expected_device_call(self, vm, action, returned=b"0\0"):
38+
def expected_device_call(
39+
self, vm, action, returned=b"0\0", klass="testclass"
40+
):
3941
self.app.expected_calls[
40-
(vm, f'admin.vm.device.testclass.{action}', None, None)] = returned
42+
(vm, f"admin.vm.device.{klass}.{action}", None, None)
43+
] = returned
4144

4245
def setUp(self):
4346
super().setUp()
@@ -174,6 +177,41 @@ def test_003_list_device_classes(self):
174177
'pci\nusb\n'
175178
)
176179

180+
def test_004_list_pci_with_sbdf(self):
181+
"""
182+
List PCI devices with SBDF info.
183+
"""
184+
self.app.expected_calls[("dom0", "admin.vm.List", None, None)] = (
185+
b"0\0dom0 class=AdminVM state=Running\n"
186+
)
187+
self.app.domains.clear_cache()
188+
self.expected_device_call(
189+
"dom0",
190+
"Available",
191+
b"0\00000_14.0:0x8086:0xa0ed::p0c0330 "
192+
b"device_id='0x8086:0xa0ed::p0c0330' port_id='00_14.0' "
193+
b"devclass='pci' backend_domain='dom0' product='p1' vendor='v' "
194+
b"interfaces='p0c0330' _sbdf='0000:00:14.0'\n"
195+
b"00_1d.0-00_00.0:0x8086:0x2725::p028000 "
196+
b"device_id='0x8086:0x2725::p028000' port_id='00_1d.0-00_00.0' "
197+
b"devclass='pci' backend_domain='dom0' product='p2' vendor='v' "
198+
b"interfaces='p028000' _sbdf='0000:aa:00.0'\n",
199+
klass="pci",
200+
)
201+
self.expected_device_call("dom0", "Attached", b"0\0", klass="pci")
202+
203+
with qubesadmin.tests.tools.StdoutBuffer() as buf:
204+
qubesadmin.tools.qvm_device.main(
205+
["pci", "list", "--with-sbdf", "dom0"], app=self.app
206+
)
207+
self.assertEqual(
208+
[x.rstrip() for x in buf.getvalue().splitlines()],
209+
[
210+
"dom0:00_14.0 0000:00:14.0 PCI_USB: v p1",
211+
"dom0:00_1d.0-00_00.0 0000:aa:00.0 Network: v p2",
212+
],
213+
)
214+
177215
def test_010_attach(self):
178216
""" Test attach action """
179217
self.app.expected_calls[(

qubesadmin/tools/qvm_device.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from qubesadmin.devices import DEVICE_DENY_LIST
4545

4646

47-
def prepare_table(dev_list):
47+
def prepare_table(dev_list, with_sbdf=False):
4848
"""Converts a list of :py:class:`qubes.devices.DeviceInfo` objects to a
4949
list of tuples for the :py:func:`qubes.tools.print_table`.
5050
@@ -53,21 +53,35 @@ def prepare_table(dev_list):
5353
5454
:param iterable dev_list: List of :py:class:`qubes.devices.DeviceInfo`
5555
objects.
56+
:param bool with_sbdf: when True, include SBDF identifier of PCI device
5657
:returns: list of tuples
5758
"""
5859
output = []
5960
header = []
6061
if sys.stdout.isatty():
61-
header += [("BACKEND:DEVID", "DESCRIPTION", "USED BY")] # NOQA
62+
if with_sbdf:
63+
header += [("BACKEND:DEVID", "SBDF", "DESCRIPTION", "USED BY")]
64+
else:
65+
header += [("BACKEND:DEVID", "DESCRIPTION", "USED BY")]
6266

6367
for line in dev_list:
64-
output += [
65-
(
66-
line.ident,
67-
line.description,
68-
str(line.assignments),
69-
)
70-
]
68+
if with_sbdf:
69+
output += [
70+
(
71+
line.ident,
72+
line.sbdf,
73+
line.description,
74+
str(line.assignments),
75+
)
76+
]
77+
else:
78+
output += [
79+
(
80+
line.ident,
81+
line.description,
82+
str(line.assignments),
83+
)
84+
]
7185

7286
return header + sorted(output)
7387

@@ -81,6 +95,7 @@ def __init__(self, device: DeviceInfo, assignment=False):
8195
self.description = device.description
8296
self.assignment = assignment
8397
self.frontends = []
98+
self.sbdf = getattr(device, "data", {}).get("sbdf")
8499

85100
@property
86101
def assignments(self):
@@ -96,6 +111,8 @@ def list_devices(args):
96111
"""
97112
Called by the parser to execute the qubes-devices list subcommand."""
98113
domains = args.domains if hasattr(args, "domains") else None
114+
if args.devclass != "pci":
115+
args.with_sbdf = False
99116
lines = _load_lines(args.app, domains, args.devclass, actual_devices=True)
100117
lines = list(lines.values())
101118
# short command without (list/ls) should print just existing devices
@@ -107,7 +124,9 @@ def list_devices(args):
107124
args.app, [], args.devclass, actual_devices=False
108125
)
109126
lines += list(extra_lines.values())
110-
qubesadmin.tools.print_table(prepare_table(lines))
127+
qubesadmin.tools.print_table(
128+
prepare_table(lines, with_sbdf=getattr(args, "with_sbdf"))
129+
)
111130

112131

113132
def _load_lines(app, domains, devclass, actual_devices: bool):
@@ -469,6 +488,13 @@ def init_list_parser(sub_parsers):
469488
"indicated by '*' before qube name.",
470489
)
471490

491+
list_parser.add_argument(
492+
"--with-sbdf",
493+
"--resolve-paths",
494+
action="store_true",
495+
help="Include resolved PCI path (SBDF) identifier of the PCI "
496+
"devices; ignored for non-PCI devices",
497+
)
472498
vm_name_group = qubesadmin.tools.VmNameGroup(
473499
list_parser,
474500
required=False,

0 commit comments

Comments
 (0)