diff --git a/doc/example.xml b/doc/example.xml index c33c232cf..83711910e 100644 --- a/doc/example.xml +++ b/doc/example.xml @@ -22,7 +22,7 @@ - + diff --git a/doc/qubes-devices.rst b/doc/qubes-devices.rst index 9018fec8d..9c382945f 100644 --- a/doc/qubes-devices.rst +++ b/doc/qubes-devices.rst @@ -189,6 +189,75 @@ We must first create an assignment (`assign`) as required attached upon each VM startup. However, if a PCI device is currently in use by another VM, the startup of the second VM will fail. +PCI devices addressing +^^^^^^^^^^^^^^^^^^^^^^ + +Qubes identifies PCI devices using a PCI path, instead of just +segment/bus/device/function (aka SBDF). The latter is used by many tools (like +`lspci`) but it's not guaranteed to remain stable across (seemingly unrelated) +hardware or firmware changes. The PCI path is built by following PCIe bridges +from the root port down to the target device. The path is constructed as +follows: + +1. It starts with the root port address written as +`[_]_.` (the segment part can be omitted if +`0000`). All numbers written in hex, with `` (if present) padded to +4 digits, `` and `` padded to 2 digits and `` as 1 digit. +2. Subsequent bridges are added after a dash (`-`) in form of +`_.`, where `` is a bus number +relative to the parent bridge's secondary bus number. In a simple case where +the parent bridge has only one bus, the `` will be `00`. +3. Final device is added the same way as the bridges described in the second +step. + +The path uses `` instead of bridge's BUS number directly, to +remain stable across adding/removing *other* bridges. + +For example, given the following PCI devices layout: + +.. code-block:: + + # lspci -t + -[0000:00]-+-00.0 + +-00.2 + +-01.0 + +-02.0 + +-02.2-[01]----00.0 + +-02.4-[02]----00.0 + +-03.0 + +-03.1-[03-61]-- + +-04.0 + +-04.1-[62-c0]-- + +-08.0 + +-08.1-[c1]--+-00.0 + | +-00.1 + | +-00.2 + | +-00.3 + | +-00.4 + | +-00.5 + | \-00.6 + ... + + +The device 0000:c1:00.0 is behind bridge at 0000:00:08.1. In some cases there +may be more bridges. There may be also more devices behind a single bridge - in +the example above 0000:c1:00.0 is just a single multi-function device, but +bridge 0000:00:03.1 can have multiple devices (covering buses `03` up to `61`). +You can get bus ranges handled by a given bridge by looking at "secondary" +and "subordinate" attributes on lspci (and similarly in sysfs): + +.. code-block:: + + 00:08.1 PCI bridge: Advanced Micro Devices, Inc. [AMD] Phoenix Internal GPP Bridge to Bus [C:A] (prog-if 00 [Normal decode]) + Subsystem: Device 0006:f111 + Flags: bus master, fast devsel, latency 0, IRQ 106 + Bus: primary=00, secondary=c1, subordinate=c1, sec-latency=0 + ... + +The path for the 0000:c1:00.0 device is: `0000_00_08.1-00_00.0`, where the -00 +part means "the first bus behind this bridge". Since the segment is `0000`, +this path can be written also as `00_08.1-00_00.0`. + Microphone ---------- diff --git a/qubes/ext/pci.py b/qubes/ext/pci.py index d7e810c51..0571da337 100644 --- a/qubes/ext/pci.py +++ b/qubes/ext/pci.py @@ -35,6 +35,7 @@ import qubes.devices import qubes.ext from qubes.device_protocol import Port +from qubes.utils import sbdf_to_path, path_to_sbdf, is_pci_path #: cache of PCI device classes pci_classes = None @@ -80,18 +81,9 @@ def load_pci_classes(): def pcidev_class(dev_xmldesc): - sysfs_path = dev_xmldesc.findtext("path") - assert sysfs_path - try: - with open(sysfs_path + "/class", encoding="ascii") as f_class: - class_id = f_class.read().strip() - except OSError: - return "unknown" - + class_id = pcidev_interface(dev_xmldesc) if not qubes.ext.pci.pci_classes: qubes.ext.pci.pci_classes = load_pci_classes() - if class_id.startswith("0x"): - class_id = class_id[2:] try: # ignore prog-if return qubes.ext.pci.pci_classes[class_id[0:4]] @@ -147,12 +139,12 @@ def _device_desc(hostdev_xml): class PCIDevice(qubes.device_protocol.DeviceInfo): # pylint: disable=too-few-public-methods regex = re.compile( - r"\A(?P[0-9a-f]+)_(?P[0-9a-f]+)\." - r"(?P[0-9a-f]+)\Z" + r"\A((?P[0-9a-f]{4})[_:])?(?P[0-9a-f]{2})[_:]" + r"(?P[0-9a-f]{2})\.(?P[0-9a-f])\Z" ) _libvirt_regex = re.compile( - r"\Apci_0000_(?P[0-9a-f]+)_(?P[0-9a-f]+)_" - r"(?P[0-9a-f]+)\Z" + r"\Apci_(?P[0-9a-f]{4})_(?P[0-9a-f]{2})_" + r"(?P[0-9a-f]{2})_(?P[0-9a-f])\Z" ) def __init__(self, port: Port, libvirt_name=None): @@ -160,9 +152,7 @@ def __init__(self, port: Port, libvirt_name=None): dev_match = self._libvirt_regex.match(libvirt_name) if not dev_match: raise UnsupportedDevice(libvirt_name) - port_id = "{bus}_{device}.{function}".format( - **dev_match.groupdict() - ) + port_id = sbdf_to_path(libvirt_name) port = Port( backend_domain=port.backend_domain, port_id=port_id, @@ -171,14 +161,24 @@ def __init__(self, port: Port, libvirt_name=None): super().__init__(port) - dev_match = self.regex.match(port.port_id) + if is_pci_path(port.port_id): + sbdf = path_to_sbdf(port.port_id) + else: + sbdf = port.port_id + dev_match = self.regex.match(sbdf) if not dev_match: raise ValueError( - "Invalid device identifier: {!r}".format(port.port_id) + "Invalid device identifier: {!r} (sbdf: {!r})".format( + port.port_id, sbdf + ) ) + self.data["sbdf"] = sbdf + for group in self.regex.groupindex: setattr(self, group, dev_match.group(group)) + if getattr(self, "segment") is None: + self.segment = "0000" # lazy loading self._description: Optional[str] = None @@ -258,7 +258,7 @@ def parent_device(self) -> Optional[qubes.device_protocol.DeviceInfo]: def libvirt_name(self): # pylint: disable=no-member # noinspection PyUnresolvedReferences - return f"pci_0000_{self.bus}_{self.device}_{self.function}" + return f"pci_{self.segment}_{self.bus}_{self.device}_{self.function}" @property def description(self): @@ -389,11 +389,13 @@ def on_device_list_attached(self, vm, event, **kwargs): if hostdev.get("type") != "pci": continue address = hostdev.find("source/address") + segment = address.get("domain")[2:] bus = address.get("bus")[2:] device = address.get("slot")[2:] function = address.get("function")[2:] - port_id = "{bus}_{device}.{function}".format( + libvirt_name = "pci_{segment}_{bus}_{device}_{function}".format( + segment=segment, bus=bus, device=device, function=function, @@ -401,19 +403,17 @@ def on_device_list_attached(self, vm, event, **kwargs): yield PCIDevice( Port( backend_domain=vm.app.domains[0], - port_id=port_id, + port_id=None, devclass="pci", - ) + ), + libvirt_name=libvirt_name, ), {} @qubes.ext.handler("device-pre-attach:pci") def on_device_pre_attached_pci(self, vm, event, device, options): # pylint: disable=unused-argument - if not os.path.exists( - "/sys/bus/pci/devices/0000:{}".format( - device.port_id.replace("_", ":") - ) - ): + sbdf = path_to_sbdf(device.port_id) + if sbdf is None or not os.path.exists(f"/sys/bus/pci/devices/{sbdf}"): raise qubes.exc.QubesException( "Invalid PCI device: {}".format(device.port_id) ) @@ -467,7 +467,7 @@ def on_device_pre_detached_pci(self, vm, event, port): ) as p: result = p.communicate()[0].decode() m = re.search( - r"^(\d+.\d+)\s+0000:{}$".format(device.port_id.replace("_", ":")), + r"^(\d+.\d+)\s+{}$".format(device.data["sbdf"]), result, flags=re.MULTILINE, ) diff --git a/qubes/tests/devices_pci.py b/qubes/tests/devices_pci.py index 0d8be0f47..10704c4cd 100644 --- a/qubes/tests/devices_pci.py +++ b/qubes/tests/devices_pci.py @@ -17,11 +17,16 @@ # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, see . +import os.path +import unittest from unittest import mock import qubes.tests import qubes.ext.pci from qubes.device_protocol import DeviceInterface +from qubes.utils import sbdf_to_path, path_to_sbdf + +orig_open = open class TestVM(object): @@ -107,17 +112,53 @@ def mock_file_open(filename: str, *_args, **_kwargs): \t09 CANBUS \t80 Serial bus controller """ - elif filename.startswith("/sys/devices/pci"): - content = "0x0c0330" else: - raise OSError() + return orig_open(filename, *_args, **_kwargs) file_object = mock.mock_open(read_data=content).return_value file_object.__iter__.return_value = content return file_object -class TC_00_Block(qubes.tests.QubesTestCase): +# prefer location in git checkout +tests_sysfs_path = os.path.dirname(__file__) + "/../../tests-data/sysfs/sys" +if not os.path.exists(tests_sysfs_path): + # but if not there, look for package installed one + tests_sysfs_path = "/usr/share/qubes/tests-data/sysfs/sys" + + +@mock.patch("qubes.utils.SYSFS_BASE", tests_sysfs_path) +class TC_00_helpers(qubes.tests.QubesTestCase): + def test_000_sbdf_to_path1(self): + path = sbdf_to_path("0000:c6:00.0") + self.assertEqual(path, "c0_03.5-00_00.0-00_00.0") + + def test_001_sbdf_to_path2(self): + path = sbdf_to_path("0000:00:18.4") + self.assertEqual(path, "00_18.4") + + def test_002_sbdf_to_path_libvirt(self): + path = sbdf_to_path("pci_0000_00_18_4") + self.assertEqual(path, "00_18.4") + + def test_003_sbdf_to_path_default_segment1(self): + path = sbdf_to_path("00:18.4") + self.assertEqual(path, "00_18.4") + + def test_004_sbdf_to_path_default_segment2(self): + path = sbdf_to_path("0000:00:18.4") + self.assertEqual(path, "00_18.4") + + def test_010_path_to_sbdf1(self): + path = path_to_sbdf("0000_c0_03.5-00_00.0-00_00.0") + self.assertEqual(path, "0000:c6:00.0") + + def test_011_path_to_sbdf2(self): + path = path_to_sbdf("0000_00_18.4") + self.assertEqual(path, "0000:00:18.4") + + +class TC_10_PCI(qubes.tests.QubesTestCase): def setUp(self): super().setUp() self.ext = qubes.ext.pci.PCIDeviceExtension() @@ -143,7 +184,7 @@ def test_000_unsupported_device(self): mock.Mock( **{ "XMLDesc.return_value": PCI_XML.format( - *["1000"] * 3 + *["10000"] * 3 ), "listCaps.return_value": ["pci"], } diff --git a/qubes/tests/vm/init.py b/qubes/tests/vm/init.py index 3ad3fb4ff..158f37316 100644 --- a/qubes/tests/vm/init.py +++ b/qubes/tests/vm/init.py @@ -84,7 +84,7 @@ def setUp(self): - + @@ -125,7 +125,7 @@ def test_000_load(self): self.assertTrue( list(vm.devices["pci"].get_assigned_devices())[0].matches( - qubes.ext.pci.PCIDevice(Port(vm, "00_11.22", "pci")) + qubes.ext.pci.PCIDevice(Port(vm, "00_11.2", "pci")) ) ) diff --git a/qubes/tests/vm/qubesvm.py b/qubes/tests/vm/qubesvm.py index 09bc4f36f..eccd2e503 100644 --- a/qubes/tests/vm/qubesvm.py +++ b/qubes/tests/vm/qubesvm.py @@ -47,6 +47,12 @@ import qubes.tests import qubes.tests.vm +# prefer location in git checkout +tests_sysfs_path = os.path.dirname(__file__) + "/../../../tests-data/sysfs/sys" +if not os.path.exists(tests_sysfs_path): + # but if not there, look for package installed one + tests_sysfs_path = "/usr/share/qubes/tests-data/sysfs/sys" + class TestApp(object): labels = { @@ -928,6 +934,55 @@ def test_500_property_migrate_virt_mode(self): with self.assertRaises(AttributeError): vm.hvm + @unittest.mock.patch("qubes.utils.SYSFS_BASE", tests_sysfs_path) + def test_510_migrate_pci_assignments(self): + vm = qubes.vm.adminvm.AdminVM(self.app, None) + dom0 = self.get_vm(vm=vm) + xml_template = """ + + + 1 + testvm + + hvm + + + + + + """ + xml = lxml.etree.XML(xml_template) + vm = qubes.vm.qubesvm.QubesVM(self.app, xml) + vm.load_properties() + vm.load_extras() + dev_ass = list(vm.devices["pci"].get_assigned_devices()) + self.assertEqual(len(dev_ass), 1) + self.assertEqual(dev_ass[0].port_id, "00_08.1-00_00.2") + + def test_511_migrate_pci_assignments_non_existing(self): + vm = qubes.vm.adminvm.AdminVM(self.app, None) + dom0 = self.get_vm(vm=vm) + xml_template = """ + + + 1 + testvm + + hvm + + + + + + """ + xml = lxml.etree.XML(xml_template) + vm = qubes.vm.qubesvm.QubesVM(self.app, xml) + vm.load_properties() + vm.load_extras() + dev_ass = list(vm.devices["pci"].get_assigned_devices()) + self.assertEqual(len(dev_ass), 1) + self.assertEqual(dev_ass[0].port_id, "02_00.7") + def test_600_libvirt_xml_pv(self): my_uuid = "7db78950-c467-4863-94d1-af59806384ea" expected = f""" @@ -1527,6 +1582,7 @@ def test_600_libvirt_xml_hvm_pcidev(self):
@@ -1637,6 +1693,7 @@ def test_600_libvirt_xml_hvm_pcidev_s0ix(self):
diff --git a/qubes/utils.py b/qubes/utils.py index d1f5e3645..1ddc53798 100644 --- a/qubes/utils.py +++ b/qubes/utils.py @@ -24,6 +24,7 @@ import hashlib import logging import random +import re import string import os import os.path @@ -236,7 +237,7 @@ def replace_file( permissions, close_on_success=True, logger=LOGGER, - log_level=logging.DEBUG + log_level=logging.DEBUG, ): """Yield a tempfile whose name starts with dst. If the block does not raise an exception, apply permissions and persist the @@ -353,3 +354,142 @@ def sanitize_stderr_for_log(untrusted_stderr: bytes) -> str: b if b in allowed_bytes else b"_"[0] for b in untrusted_stderr ) return stderr.decode("ascii") + + +SYSFS_BASE = "/sys" + + +def sbdf_to_path(device_id: str): + """ + Lookup full path for a given device + + :param device_id: sbdf, for example 0000:02:03.0; accepts also libvirt + format like 0000_02_03_0 + :return: converted identifier of None if device is not found + """ + regex = re.compile( + r"\A(?:pci_)?((?P[0-9a-f]{4})[_:])?(?P[0-9a-f]{2})[_:]" + r"(?P[0-9a-f]{2})[._](?P[0-9a-f])\Z" + ) + sysfs_pci_devs_base = f"{SYSFS_BASE}/bus/pci/devices" + + dev_match = regex.match(device_id) + if not dev_match: + raise ValueError("Invalid device identifier: {!r}".format(device_id)) + if dev_match["segment"] is not None: + segment = dev_match["segment"] + else: + segment = "0000" + if dev_match["bus"] == "00": + return (f"{segment}_" if segment != "0000" else "") + ( + f"{dev_match['bus']}_" + f"{dev_match['device']}.{dev_match['function']}" + ) + sbdf = ( + f"{segment}:{dev_match['bus']}:" + f"{dev_match['device']}.{dev_match['function']}" + ) + try: + sysfs_path = os.readlink(f"{sysfs_pci_devs_base}/{sbdf}") + except FileNotFoundError: + return None + # example: ../../../devices/pci0000:00/0000:00:1a.0/0000:02:00.0 + rel_links, _, path = sysfs_path.partition(f"/pci{segment}:") + assert os.path.normpath( + os.path.join(sysfs_pci_devs_base, rel_links) + ) == os.path.normpath(f"{SYSFS_BASE}/devices") + # drop also 00/ part, which may be also 40/, 80/ etc + path = path[3:] + bus_offset = 0 + result_list = [] + for path_part in path.split("/"): + assert bus_offset != -1 + bridge_match = regex.match(path_part) + if not bridge_match: + raise ValueError("Invalid bridge found: {!r}".format(path_part)) + assert int(bridge_match["bus"], 16) >= bus_offset + bus_num = int(bridge_match["bus"], 16) - bus_offset + bridge_str = ( + f"{bus_num:02x}_{bridge_match['device']}." + f"{bridge_match['function']}" + ) + result_list.append(bridge_str) + try: + with open( + f"{sysfs_pci_devs_base}/{path_part}/secondary_bus_number", + encoding="ascii", + ) as f_bus_num: + # this one is in decimal + # this can raise ValueError, propagate it + bus_offset = int(f_bus_num.read()) + except FileNotFoundError: + # last device in chain + bus_offset = -1 + + if segment == "0000": + return "-".join(result_list) + return segment + "_" + "-".join(result_list) + + +def path_to_sbdf(path: str): + """ + Convert device path as done by *sbdf_to_path* back to SBDF + :param path: + :return: + """ + + regex = re.compile( + r"\A(?P[0-9a-f]+)[_:]" + r"(?P[0-9a-f]+)[._](?P[0-9a-f]+)\Z" + ) + segment_re = re.compile(r"\A(?P[0-9a-f]{4})[_:](?P.*)\Z") + + # default segment + segment = "0000" + bus_offset = 0 + current_dev = "" + for path_part in path.split("-"): + assert bus_offset != -1 + # first part may include segment + if bus_offset == 0: + segment_match = segment_re.match(path_part) + if segment_match: + segment = segment_match["segment"] + path_part = segment_match["rest"] + part_match = regex.match(path_part) + if not part_match: + raise ValueError( + "Invalid PCI device path at {!r}".format(path_part) + ) + bus_num = int(part_match["bus"], 16) + bus_offset + current_dev = ( + f"{segment}:{bus_num:02x}:{part_match['device']}." + f"{part_match['function']}" + ) + try: + with open( + f"{SYSFS_BASE}/bus/pci/devices/" + f"{current_dev}/secondary_bus_number", + encoding="ascii", + ) as f_bus_num: + # this one is in decimal + # this can raise ValueError, propagate it + bus_offset = int(f_bus_num.read()) + except FileNotFoundError: + # last device in chain + bus_offset = -1 + + return current_dev + + +def is_pci_path(device_id: str): + """Check if given device id is already a device path. + + :param device_id: device id to check + :return: + """ + path_re = re.compile( + r"\A([0-9a-f]{4}_)?00_[0-9a-f]{2}\.[0-9a-f]" + r"(-[0-9a-f]{2}_[0-9a-f]{2}\.[0-9a-f])*\Z" + ) + return bool(path_re.match(device_id)) diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index 840944ea8..20e6d0507 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -37,6 +37,7 @@ import qubes.events import qubes.features import qubes.log +from qubes.utils import is_pci_path, sbdf_to_path VM_ENTRY_POINT = "qubes.vm" @@ -350,11 +351,19 @@ def load_extras(self): backend = ( self.app.domains[backend_name] if backend_name else None ) + port_id = node.get("id", "*") + # migration from plain BDF to PCI path + if ( + devclass == "pci" + and port_id != "*" + and not is_pci_path(port_id) + ): + port_id = sbdf_to_path(port_id) or port_id device_assignment = qubes.device_protocol.DeviceAssignment( qubes.device_protocol.VirtualDevice( qubes.device_protocol.Port( backend_domain=backend, - port_id=node.get("id", "*"), + port_id=port_id, devclass=devclass, ), device_id=identity, diff --git a/relaxng/qubes.rng b/relaxng/qubes.rng index edf54b8a2..55da203fd 100644 --- a/relaxng/qubes.rng +++ b/relaxng/qubes.rng @@ -219,7 +219,7 @@ the parser will complain about missing combine= attribute on the second . - [0-9a-f]{2}_[0-9a-f]{2}.[0-9a-f]{2} + [0-9a-f]{2}_[0-9a-f]{2}.[0-9a-f]{1} diff --git a/rpm_spec/core-dom0.spec.in b/rpm_spec/core-dom0.spec.in index 6453f00be..f2949c594 100644 --- a/rpm_spec/core-dom0.spec.in +++ b/rpm_spec/core-dom0.spec.in @@ -568,6 +568,7 @@ done /usr/share/qubes/templates/libvirt/devices/pci.xml /usr/share/qubes/templates/libvirt/devices/net.xml /usr/share/qubes/tests-data/dispvm-open-thunderbird-attachment +/usr/share/qubes/tests-data/sysfs /usr/lib/tmpfiles.d/qubes.conf %attr(0755,root,root) /usr/lib/qubes/create-snapshot %attr(0755,root,root) /usr/lib/qubes/destroy-snapshot diff --git a/templates/libvirt/devices/pci.xml b/templates/libvirt/devices/pci.xml index 90d0c45c2..160ec193d 100644 --- a/templates/libvirt/devices/pci.xml +++ b/templates/libvirt/devices/pci.xml @@ -12,6 +12,7 @@ {% endif %} >
diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:00:08.1 b/tests-data/sysfs/sys/bus/pci/devices/0000:00:08.1 new file mode 120000 index 000000000..b3c2d2213 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:00:08.1 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:08.1 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.0 b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.0 new file mode 120000 index 000000000..8979233d2 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.0 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:18.0 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.1 b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.1 new file mode 120000 index 000000000..765e021a5 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.1 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:18.1 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.2 b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.2 new file mode 120000 index 000000000..5485bc7f4 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.2 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:18.2 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.3 b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.3 new file mode 120000 index 000000000..01573b1d4 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.3 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:18.3 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.4 b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.4 new file mode 120000 index 000000000..c9c4134a7 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:00:18.4 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:18.4 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:02:00.0 b/tests-data/sysfs/sys/bus/pci/devices/0000:02:00.0 new file mode 120000 index 000000000..ae75cda7b --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:02:00.0 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:08.1/0000:02:00.0 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:02:00.2 b/tests-data/sysfs/sys/bus/pci/devices/0000:02:00.2 new file mode 120000 index 000000000..cdea98dd2 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:02:00.2 @@ -0,0 +1 @@ +../../../devices/pci0000:00/0000:00:08.1/0000:02:00.2 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:c0:03.5 b/tests-data/sysfs/sys/bus/pci/devices/0000:c0:03.5 new file mode 120000 index 000000000..179c98bef --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:c0:03.5 @@ -0,0 +1 @@ +../../../devices/pci0000:c0/0000:c0:03.5 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:c5:00.0 b/tests-data/sysfs/sys/bus/pci/devices/0000:c5:00.0 new file mode 120000 index 000000000..bcc4bedc1 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:c5:00.0 @@ -0,0 +1 @@ +../../../devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0 \ No newline at end of file diff --git a/tests-data/sysfs/sys/bus/pci/devices/0000:c6:00.0 b/tests-data/sysfs/sys/bus/pci/devices/0000:c6:00.0 new file mode 120000 index 000000000..90ee17a60 --- /dev/null +++ b/tests-data/sysfs/sys/bus/pci/devices/0000:c6:00.0 @@ -0,0 +1 @@ +../../../devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0/0000:c6:00.0 \ No newline at end of file diff --git a/tests-data/sysfs/sys/devices/pci0000:00/0000:00:08.1/secondary_bus_number b/tests-data/sysfs/sys/devices/pci0000:00/0000:00:08.1/secondary_bus_number new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/tests-data/sysfs/sys/devices/pci0000:00/0000:00:08.1/secondary_bus_number @@ -0,0 +1 @@ +2 diff --git a/tests-data/sysfs/sys/devices/pci0000:00/0000:00:08.1/subordinate_bus_number b/tests-data/sysfs/sys/devices/pci0000:00/0000:00:08.1/subordinate_bus_number new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/tests-data/sysfs/sys/devices/pci0000:00/0000:00:08.1/subordinate_bus_number @@ -0,0 +1 @@ +2 diff --git a/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0/secondary_bus_number b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0/secondary_bus_number new file mode 100644 index 000000000..ca55a6c59 --- /dev/null +++ b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0/secondary_bus_number @@ -0,0 +1 @@ +198 diff --git a/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0/subordinate_bus_number b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0/subordinate_bus_number new file mode 100644 index 000000000..ca55a6c59 --- /dev/null +++ b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/0000:c5:00.0/subordinate_bus_number @@ -0,0 +1 @@ +198 diff --git a/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/secondary_bus_number b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/secondary_bus_number new file mode 100644 index 000000000..538165229 --- /dev/null +++ b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/secondary_bus_number @@ -0,0 +1 @@ +197 diff --git a/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/subordinate_bus_number b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/subordinate_bus_number new file mode 100644 index 000000000..ca55a6c59 --- /dev/null +++ b/tests-data/sysfs/sys/devices/pci0000:c0/0000:c0:03.5/subordinate_bus_number @@ -0,0 +1 @@ +198