4444unsupported_devices_warned = set ()
4545
4646
47+ SYSFS_PCI_DEVS_BASE = "/sys/bus/pci/devices"
48+
49+
4750class UnsupportedDevice (Exception ):
4851 pass
4952
@@ -135,25 +138,155 @@ def _device_desc(hostdev_xml):
135138 )
136139
137140
141+ def sbdf_to_path (device_id : str ):
142+ """
143+ Lookup full path for a given device
144+
145+ :param device_id: sbdf, for example 0000:02:03.0; accepts also libvirt
146+ format like 0000_02_03_0
147+ :return: converted identifier of None if device is not found
148+ """
149+ regex = re .compile (
150+ r"\A(?:pci_)?((?P<segment>[0-9a-f]{4})[_:])?(?P<bus>[0-9a-f]{2})[_:]"
151+ r"(?P<device>[0-9a-f]{2})[._](?P<function>[0-9a-f])\Z"
152+ )
153+
154+ dev_match = regex .match (device_id )
155+ if not dev_match :
156+ raise ValueError ("Invalid device identifier: {!r}" .format (device_id ))
157+ if dev_match ["segment" ] is not None :
158+ segment = dev_match ["segment" ]
159+ else :
160+ segment = "0000"
161+ if int (dev_match ["bus" ], 16 ) == 0 :
162+ return (f"{ segment } _" if segment != "0000" else "" ) + (
163+ f"{ dev_match ['bus' ]} _"
164+ f"{ dev_match ['device' ]} .{ dev_match ['function' ]} "
165+ )
166+ sbdf = (
167+ f"{ segment } :{ dev_match ['bus' ]} :"
168+ f"{ dev_match ['device' ]} .{ dev_match ['function' ]} "
169+ )
170+ try :
171+ sysfs_path = os .readlink (f"{ SYSFS_PCI_DEVS_BASE } /{ sbdf } " )
172+ except FileNotFoundError :
173+ return None
174+ # example: ../../../devices/pci0000:00/0000:00:1a.0/0000:02:00.0
175+ path = sysfs_path .partition (f"/pci{ segment } :" )[2 ]
176+ # drop also 00/ part, which may be also 40/, 80/ etc
177+ path = path [3 :]
178+ bus_offset = 0
179+ result_list = []
180+ for path_part in path .split ("/" ):
181+ assert bus_offset != - 1
182+ bridge_match = regex .match (path_part )
183+ if not bridge_match :
184+ raise ValueError ("Invalid bridge found: {!r}" .format (path_part ))
185+ assert int (bridge_match ["bus" ], 16 ) >= bus_offset
186+ bus_num = int (bridge_match ["bus" ], 16 ) - bus_offset
187+ bridge_str = (
188+ f"{ bus_num :02x} _{ bridge_match ['device' ]} ."
189+ f"{ bridge_match ['function' ]} "
190+ )
191+ result_list .append (bridge_str )
192+ try :
193+ with open (
194+ f"{ SYSFS_PCI_DEVS_BASE } /{ path_part } /secondary_bus_number" ,
195+ encoding = "ascii" ,
196+ ) as f_bus_num :
197+ # this one is in decimal
198+ # this can raise ValueError, propagate it
199+ bus_offset = int (f_bus_num .read ())
200+ except FileNotFoundError :
201+ # last device in chain
202+ bus_offset = - 1
203+
204+ if segment == "0000" :
205+ return "-" .join (result_list )
206+ return segment + "_" + "-" .join (result_list )
207+
208+
209+ def path_to_sbdf (path : str ):
210+ """
211+ Convert device path as done by *sbdf_to_path* back to SBDF
212+ :param path:
213+ :return:
214+ """
215+
216+ regex = re .compile (
217+ r"\A(?P<bus>[0-9a-f]+)[_:]"
218+ r"(?P<device>[0-9a-f]+)[._](?P<function>[0-9a-f]+)\Z"
219+ )
220+
221+ segment_re = re .compile (r"\A(?P<segment>[0-9a-f]{4})[_:](?P<rest>.*)\Z" )
222+
223+ # default segment
224+ segment = "0000"
225+ bus_offset = 0
226+ current_dev = ""
227+ for path_part in path .split ("-" ):
228+ assert bus_offset != - 1
229+ # first part may include segment
230+ if bus_offset == 0 :
231+ segment_match = segment_re .match (path_part )
232+ if segment_match :
233+ segment = segment_match ["segment" ]
234+ path_part = segment_match ["rest" ]
235+ part_match = regex .match (path_part )
236+ if not part_match :
237+ raise ValueError (
238+ "Invalid PCI device path at {!r}" .format (path_part )
239+ )
240+ bus_num = int (part_match ["bus" ], 16 ) + bus_offset
241+ current_dev = (
242+ f"{ segment } :{ bus_num :02x} :{ part_match ['device' ]} ."
243+ f"{ part_match ['function' ]} "
244+ )
245+ try :
246+ with open (
247+ f"{ SYSFS_PCI_DEVS_BASE } /" f"{ current_dev } /secondary_bus_number" ,
248+ encoding = "ascii" ,
249+ ) as f_bus_num :
250+ # this one is in decimal
251+ # this can raise ValueError, propagate it
252+ bus_offset = int (f_bus_num .read ())
253+ except FileNotFoundError :
254+ # last device in chain
255+ bus_offset = - 1
256+
257+ return current_dev
258+
259+
260+ def is_pci_path (device_id : str ):
261+ """Check if given device id is already a device path.
262+
263+ :param device_id: device id to check
264+ :return:
265+ """
266+ path_re = re .compile (
267+ r"\A([0-9a-f]{4}_)?00_[0-9a-f]{2}\.[0-9a-f]"
268+ r"(-[0-9a-f]{2}_[0-9a-f]{2}\.[0-9a-f])*\Z"
269+ )
270+ return bool (path_re .match (device_id ))
271+
272+
138273class PCIDevice (qubes .device_protocol .DeviceInfo ):
139274 # pylint: disable=too-few-public-methods
140275 regex = re .compile (
141- r"\A(?P<bus >[0-9a-f]+)_ (?P<device >[0-9a-f]+)\. "
142- r"(?P<function>[0-9a-f]+ )\Z"
276+ r"\A(( ?P<segment >[0-9a-f]{4})[_:])? (?P<bus >[0-9a-f]{2})[_:] "
277+ r"(?P<device>[0-9a-f]{2})\.(?P< function>[0-9a-f])\Z"
143278 )
144279 _libvirt_regex = re .compile (
145- r"\Apci_0000_ (?P<bus >[0-9a-f]+ )_(?P<device >[0-9a-f]+ )_"
146- r"(?P<function>[0-9a-f]+ )\Z"
280+ r"\Apci_ (?P<segment >[0-9a-f]{4} )_(?P<bus >[0-9a-f]{2} )_"
281+ r"(?P<device>[0-9a-f]{2})_(?P< function>[0-9a-f])\Z"
147282 )
148283
149284 def __init__ (self , port : Port , libvirt_name = None ):
150285 if libvirt_name :
151286 dev_match = self ._libvirt_regex .match (libvirt_name )
152287 if not dev_match :
153288 raise UnsupportedDevice (libvirt_name )
154- port_id = "{bus}_{device}.{function}" .format (
155- ** dev_match .groupdict ()
156- )
289+ port_id = sbdf_to_path (libvirt_name )
157290 port = Port (
158291 backend_domain = port .backend_domain ,
159292 port_id = port_id ,
@@ -162,14 +295,24 @@ def __init__(self, port: Port, libvirt_name=None):
162295
163296 super ().__init__ (port )
164297
165- dev_match = self .regex .match (port .port_id )
298+ if is_pci_path (port .port_id ):
299+ sbdf = path_to_sbdf (port .port_id )
300+ else :
301+ sbdf = port .port_id
302+ dev_match = self .regex .match (sbdf )
166303 if not dev_match :
167304 raise ValueError (
168- "Invalid device identifier: {!r}" .format (port .port_id )
305+ "Invalid device identifier: {!r} (sbdf: {!r})" .format (
306+ port .port_id , sbdf
307+ )
169308 )
170309
310+ self .sbdf = sbdf
311+
171312 for group in self .regex .groupindex :
172313 setattr (self , group , dev_match .group (group ))
314+ if getattr (self , "segment" ) is None :
315+ self .segment = "0000"
173316
174317 # lazy loading
175318 self ._description : Optional [str ] = None
@@ -249,7 +392,7 @@ def parent_device(self) -> Optional[qubes.device_protocol.DeviceInfo]:
249392 def libvirt_name (self ):
250393 # pylint: disable=no-member
251394 # noinspection PyUnresolvedReferences
252- return f"pci_0000_ { self .bus } _{ self .device } _{ self .function } "
395+ return f"pci_ { self . segment } _ { self .bus } _{ self .device } _{ self .function } "
253396
254397 @property
255398 def description (self ):
@@ -380,31 +523,31 @@ def on_device_list_attached(self, vm, event, **kwargs):
380523 if hostdev .get ("type" ) != "pci" :
381524 continue
382525 address = hostdev .find ("source/address" )
526+ segment = address .get ("domain" )[2 :]
383527 bus = address .get ("bus" )[2 :]
384528 device = address .get ("slot" )[2 :]
385529 function = address .get ("function" )[2 :]
386530
387- port_id = "{bus}_{device}.{function}" .format (
531+ libvirt_name = "pci_{segment}_{bus}_{device}_{function}" .format (
532+ segment = segment ,
388533 bus = bus ,
389534 device = device ,
390535 function = function ,
391536 )
392537 yield PCIDevice (
393538 Port (
394539 backend_domain = vm .app .domains [0 ],
395- port_id = port_id ,
540+ port_id = None ,
396541 devclass = "pci" ,
397- )
542+ ),
543+ libvirt_name = libvirt_name ,
398544 ), {}
399545
400546 @qubes .ext .handler ("device-pre-attach:pci" )
401547 def on_device_pre_attached_pci (self , vm , event , device , options ):
402548 # pylint: disable=unused-argument
403- if not os .path .exists (
404- "/sys/bus/pci/devices/0000:{}" .format (
405- device .port_id .replace ("_" , ":" )
406- )
407- ):
549+ sbdf = path_to_sbdf (device .port_id )
550+ if sbdf is None or not os .path .exists (f"/sys/bus/pci/devices/{ sbdf } " ):
408551 raise qubes .exc .QubesException (
409552 "Invalid PCI device: {}" .format (device .port_id )
410553 )
@@ -458,7 +601,7 @@ def on_device_pre_detached_pci(self, vm, event, port):
458601 ) as p :
459602 result = p .communicate ()[0 ].decode ()
460603 m = re .search (
461- r"^(\d+.\d+)\s+0000: {}$" .format (device .port_id . replace ( "_" , ":" ) ),
604+ r"^(\d+.\d+)\s+{}$" .format (device .sbdf ),
462605 result ,
463606 flags = re .MULTILINE ,
464607 )
0 commit comments