Skip to content

Commit 02a9b4f

Browse files
committed
Merge branch 'pci-path'
* pci-path: qvm-pci: optionally list SBDF of listed devices
2 parents 52083d1 + d7fe806 commit 02a9b4f

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)