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,153 @@ 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 format
146+ 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+ bridge_match = regex .match (path_part )
182+ if not bridge_match :
183+ raise ValueError ("Invalid bridge found: {!r}" .format (path_part ))
184+ assert int (bridge_match ["bus" ], 16 ) >= bus_offset
185+ bus_num = int (bridge_match ["bus" ], 16 ) - bus_offset
186+ bridge_str = (
187+ f"{ bus_num :02x} _{ bridge_match ['device' ]} ."
188+ f"{ bridge_match ['function' ]} "
189+ )
190+ result_list .append (bridge_str )
191+ try :
192+ with open (
193+ f"{ SYSFS_PCI_DEVS_BASE } /{ path_part } /secondary_bus_number"
194+ ) as f :
195+ # this one is in decimal
196+ # this can raise ValueError, propagate it
197+ bus_offset = int (f .read ())
198+ except FileNotFoundError :
199+ # last device in chain, set to None so any further arithmetic
200+ # will fail
201+ bus_offset = None
202+
203+ if segment == "0000" :
204+ return "-" .join (result_list )
205+ return segment + "_" + "-" .join (result_list )
206+
207+
208+ def path_to_sbdf (path : str ):
209+ """
210+ Convert device path as done by *sbdf_to_path* back to SBDF
211+ :param path:
212+ :return:
213+ """
214+
215+ regex = re .compile (
216+ r"\A(?P<bus>[0-9a-f]+)[_:]"
217+ r"(?P<device>[0-9a-f]+)[._](?P<function>[0-9a-f]+)\Z"
218+ )
219+
220+ segment_re = re .compile (r"\A(?P<segment>[0-9a-f]{4})[_:](?P<rest>.*)\Z" )
221+
222+ # default segment
223+ segment = "0000"
224+ bus_offset = 0
225+ current_dev = ""
226+ for path_part in path .split ("-" ):
227+ # first part may include segment
228+ if bus_offset == 0 :
229+ segment_match = segment_re .match (path_part )
230+ if segment_match :
231+ segment = segment_match ["segment" ]
232+ path_part = segment_match ["rest" ]
233+ part_match = regex .match (path_part )
234+ if not part_match :
235+ raise ValueError (
236+ "Invalid PCI device path at {!r}" .format (path_part )
237+ )
238+ bus_num = int (part_match ["bus" ], 16 ) + bus_offset
239+ current_dev = (
240+ f"{ segment } :{ bus_num :02x} :{ part_match ['device' ]} ."
241+ f"{ part_match ['function' ]} "
242+ )
243+ try :
244+ with open (
245+ f"{ SYSFS_PCI_DEVS_BASE } /" f"{ current_dev } /secondary_bus_number"
246+ ) as f :
247+ # this one is in decimal
248+ # this can raise ValueError, propagate it
249+ bus_offset = int (f .read ())
250+ except FileNotFoundError :
251+ # last device in chain, set to None so any further arithmetic
252+ # will fail
253+ bus_offset = None
254+
255+ return current_dev
256+
257+
258+ def is_pci_path (device_id : str ):
259+ """Check if given device id is already a device path.
260+
261+ :param device_id: device id to check
262+ :return:
263+ """
264+ path_re = re .compile (
265+ r"\A([0-9a-f]{4}_)?00_[0-9a-f]{2}\.[0-9a-f]"
266+ r"(-[0-9a-f]{2}_[0-9a-f]{2}\.[0-9a-f])*\Z"
267+ )
268+ return bool (path_re .match (device_id ))
269+
270+
138271class PCIDevice (qubes .device_protocol .DeviceInfo ):
139272 # pylint: disable=too-few-public-methods
140273 regex = re .compile (
141- r"\A(?P<bus >[0-9a-f]+)_ (?P<device >[0-9a-f]+)\. "
142- r"(?P<function>[0-9a-f]+ )\Z"
274+ r"\A(( ?P<segment >[0-9a-f]{4})[_:])? (?P<bus >[0-9a-f]{2})[_:] "
275+ r"(?P<device>[0-9a-f]{2})\.(?P< function>[0-9a-f])\Z"
143276 )
144277 _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"
278+ r"\Apci_ (?P<segment >[0-9a-f]{4} )_(?P<bus >[0-9a-f]{2} )_"
279+ r"(?P<device>[0-9a-f]{2})_(?P< function>[0-9a-f])\Z"
147280 )
148281
149282 def __init__ (self , port : Port , libvirt_name = None ):
150283 if libvirt_name :
151284 dev_match = self ._libvirt_regex .match (libvirt_name )
152285 if not dev_match :
153286 raise UnsupportedDevice (libvirt_name )
154- port_id = "{bus}_{device}.{function}" .format (
155- ** dev_match .groupdict ()
156- )
287+ port_id = sbdf_to_path (libvirt_name )
157288 port = Port (
158289 backend_domain = port .backend_domain ,
159290 port_id = port_id ,
@@ -162,14 +293,24 @@ def __init__(self, port: Port, libvirt_name=None):
162293
163294 super ().__init__ (port )
164295
165- dev_match = self .regex .match (port .port_id )
296+ if is_pci_path (port .port_id ):
297+ sbdf = path_to_sbdf (port .port_id )
298+ else :
299+ sbdf = port .port_id
300+ dev_match = self .regex .match (sbdf )
166301 if not dev_match :
167302 raise ValueError (
168- "Invalid device identifier: {!r}" .format (port .port_id )
303+ "Invalid device identifier: {!r} (sbdf: {!r})" .format (
304+ port .port_id , sbdf
305+ )
169306 )
170307
308+ self .sbdf = sbdf
309+
171310 for group in self .regex .groupindex :
172311 setattr (self , group , dev_match .group (group ))
312+ if self .segment is None :
313+ self .segment = "0000"
173314
174315 # lazy loading
175316 self ._description : Optional [str ] = None
@@ -249,7 +390,7 @@ def parent_device(self) -> Optional[qubes.device_protocol.DeviceInfo]:
249390 def libvirt_name (self ):
250391 # pylint: disable=no-member
251392 # noinspection PyUnresolvedReferences
252- return f"pci_0000_ { self .bus } _{ self .device } _{ self .function } "
393+ return f"pci_ { self . segment } _ { self .bus } _{ self .device } _{ self .function } "
253394
254395 @property
255396 def description (self ):
@@ -380,31 +521,31 @@ def on_device_list_attached(self, vm, event, **kwargs):
380521 if hostdev .get ("type" ) != "pci" :
381522 continue
382523 address = hostdev .find ("source/address" )
524+ segment = address .get ("domain" )[2 :]
383525 bus = address .get ("bus" )[2 :]
384526 device = address .get ("slot" )[2 :]
385527 function = address .get ("function" )[2 :]
386528
387- port_id = "{bus}_{device}.{function}" .format (
529+ libvirt_name = "pci_{segment}_{bus}_{device}_{function}" .format (
530+ segment = segment ,
388531 bus = bus ,
389532 device = device ,
390533 function = function ,
391534 )
392535 yield PCIDevice (
393536 Port (
394537 backend_domain = vm .app .domains [0 ],
395- port_id = port_id ,
538+ port_id = None ,
396539 devclass = "pci" ,
397- )
540+ ),
541+ libvirt_name = libvirt_name ,
398542 ), {}
399543
400544 @qubes .ext .handler ("device-pre-attach:pci" )
401545 def on_device_pre_attached_pci (self , vm , event , device , options ):
402546 # pylint: disable=unused-argument
403- if not os .path .exists (
404- "/sys/bus/pci/devices/0000:{}" .format (
405- device .port_id .replace ("_" , ":" )
406- )
407- ):
547+ sbdf = path_to_sbdf (device .port_id )
548+ if sbdf is None or not os .path .exists (f"/sys/bus/pci/devices/{ sbdf } " ):
408549 raise qubes .exc .QubesException (
409550 "Invalid PCI device: {}" .format (device .port_id )
410551 )
@@ -458,7 +599,7 @@ def on_device_pre_detached_pci(self, vm, event, port):
458599 ) as p :
459600 result = p .communicate ()[0 ].decode ()
460601 m = re .search (
461- r"^(\d+.\d+)\s+0000: {}$" .format (device .port_id . replace ( "_" , ":" ) ),
602+ r"^(\d+.\d+)\s+{}$" .format (device .sbdf ),
462603 result ,
463604 flags = re .MULTILINE ,
464605 )
0 commit comments