@@ -10,12 +10,9 @@ use std::time::Duration;
1010pub ( crate ) const VFIO_BIND_POLL_INTERVAL : Duration = Duration :: from_millis ( 100 ) ;
1111pub ( crate ) const VFIO_BIND_MAX_POLL_ATTEMPTS : u32 = 20 ;
1212
13- /// Reference counter for vendor:device ID registrations in the vfio-pci
14- /// match table. Multiple devices may share the same vendor:device pair. We
15- /// only write to the kernel's `new_id`/`remove_id` sysfs files when the first
16- /// device registers or the last device deregisters an ID.
17- static VFIO_ID_REFCOUNTS : LazyLock < Mutex < HashMap < String , usize > > > =
18- LazyLock :: new ( || Mutex :: new ( HashMap :: new ( ) ) ) ;
13+ // Process-local registry shared across bind guards to coordinate vfio-pci ID writes.
14+ static VFIO_ID_REGISTRY : LazyLock < Mutex < VfioIdRegistry > > =
15+ LazyLock :: new ( || Mutex :: new ( VfioIdRegistry :: default ( ) ) ) ;
1916
2017pub ( crate ) fn current_driver_name ( sysfs : & SysfsRoot , bdf : & str ) -> Option < String > {
2118 sysfs. pci_device_ref ( bdf) . driver_name ( )
@@ -37,14 +34,9 @@ pub(crate) fn register_vfio_new_id(sysfs: &SysfsRoot, bdf: &str) {
3734 return ;
3835 } ;
3936
40- let should_write = {
41- let mut map = VFIO_ID_REFCOUNTS . lock ( ) . unwrap ( ) ;
42- let count = map. entry ( id_str. clone ( ) ) . or_insert ( 0 ) ;
43- * count += 1 ;
44- * count == 1
45- } ;
37+ let registration = VFIO_ID_REGISTRY . lock ( ) . unwrap ( ) . register ( & id_str) ;
4638
47- if !should_write {
39+ if registration != VfioIdRegistration :: FirstUser {
4840 tracing:: debug!(
4941 bdf, id = %id_str,
5042 "vfio-pci new_id already registered by another device, refcount incremented"
@@ -80,37 +72,29 @@ pub(crate) fn deregister_vfio_new_id(sysfs: &SysfsRoot, bdf: &str) {
8072 return ;
8173 } ;
8274
83- let should_write = {
84- let mut map = VFIO_ID_REFCOUNTS . lock ( ) . unwrap ( ) ;
85- match map. get_mut ( & id_str) {
86- Some ( count) if * count > 1 => {
87- * count -= 1 ;
88- false
89- }
90- Some ( _) => {
91- map. remove ( & id_str) ;
92- true
93- }
94- None => true ,
95- }
96- } ;
75+ deregister_vfio_id ( sysfs, & id_str, Some ( bdf) ) ;
76+ }
77+
78+ fn deregister_vfio_id ( sysfs : & SysfsRoot , id_str : & str , bdf : Option < & str > ) {
79+ let deregistration = VFIO_ID_REGISTRY . lock ( ) . unwrap ( ) . deregister ( id_str) ;
9780
98- if !should_write {
81+ if deregistration == VfioIdDeregistration :: StillInUse {
9982 tracing:: debug!(
100- bdf, id = %id_str,
83+ bdf = ?bdf , id = %id_str,
10184 "vfio-pci remove_id skipped (other devices still using this ID)"
10285 ) ;
10386 return ;
10487 }
10588
89+ // LastUser and NotTracked both require a best-effort remove_id write.
10690 let remove_id_path = sysfs. vfio_pci_remove_id ( ) ;
107- match write_sysfs ( & remove_id_path, & id_str) {
91+ match write_sysfs ( & remove_id_path, id_str) {
10892 Ok ( ( ) ) => {
109- tracing:: debug!( bdf, id = %id_str, "deregistered vfio-pci new_id" ) ;
93+ tracing:: debug!( bdf = ?bdf , id = %id_str, "deregistered vfio-pci new_id" ) ;
11094 }
11195 Err ( _) => {
11296 tracing:: debug!(
113- bdf, id = %id_str,
97+ bdf = ?bdf , id = %id_str,
11498 "vfio-pci remove_id write skipped (not registered or already removed)"
11599 ) ;
116100 }
@@ -123,23 +107,12 @@ pub(crate) fn deregister_vfio_new_id(sysfs: &SysfsRoot, bdf: &str) {
123107/// sysfs at call time, making it reliable even when the device has been
124108/// physically removed or sysfs is otherwise inaccessible.
125109pub ( crate ) fn deregister_vfio_id_by_value ( sysfs : & SysfsRoot , id_str : & str ) {
126- let remove_id_path = sysfs. vfio_pci_remove_id ( ) ;
127- match write_sysfs ( & remove_id_path, id_str) {
128- Ok ( ( ) ) => {
129- tracing:: debug!( id = %id_str, "deregistered vfio-pci new_id (by cached value)" ) ;
130- }
131- Err ( _) => {
132- tracing:: debug!(
133- id = %id_str,
134- "vfio-pci remove_id write skipped (not registered or already removed)"
135- ) ;
136- }
137- }
110+ deregister_vfio_id ( sysfs, id_str, None ) ;
138111}
139112
140113pub ( crate ) fn clear_vfio_id_refcounts ( ) {
141- if let Ok ( mut map ) = VFIO_ID_REFCOUNTS . lock ( ) {
142- map . clear ( ) ;
114+ if let Ok ( mut registry ) = VFIO_ID_REGISTRY . lock ( ) {
115+ registry . clear ( ) ;
143116 }
144117}
145118
@@ -280,9 +253,72 @@ pub(crate) fn restore_to_host_driver_ex(
280253 Ok ( ( ) )
281254}
282255
256+ /// Process-local reference counter for vendor:device ID registrations in the
257+ /// vfio-pci match table. Multiple devices may share the same vendor:device
258+ /// pair. We only write to the kernel's `new_id`/`remove_id` sysfs files when
259+ /// the first device registers or the last device deregisters an ID.
260+ #[ derive( Debug , Default ) ]
261+ struct VfioIdRegistry {
262+ refcounts : HashMap < String , usize > ,
263+ }
264+
265+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
266+ enum VfioIdRegistration {
267+ FirstUser ,
268+ AlreadyTracked ,
269+ }
270+
271+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
272+ enum VfioIdDeregistration {
273+ LastUser ,
274+ StillInUse ,
275+ NotTracked ,
276+ }
277+
278+ impl VfioIdRegistry {
279+ /// Record one active user of `id`.
280+ ///
281+ /// Returns the resulting process-local registration state.
282+ fn register ( & mut self , id : & str ) -> VfioIdRegistration {
283+ let count = self . refcounts . entry ( id. to_string ( ) ) . or_insert ( 0 ) ;
284+ * count += 1 ;
285+ if * count == 1 {
286+ VfioIdRegistration :: FirstUser
287+ } else {
288+ VfioIdRegistration :: AlreadyTracked
289+ }
290+ }
291+
292+ /// Record one fewer active user of `id`.
293+ ///
294+ /// Returns the resulting process-local deregistration state.
295+ fn deregister ( & mut self , id : & str ) -> VfioIdDeregistration {
296+ match self . refcounts . get_mut ( id) {
297+ Some ( count) if * count > 1 => {
298+ * count -= 1 ;
299+ VfioIdDeregistration :: StillInUse
300+ }
301+ Some ( _) => {
302+ self . refcounts . remove ( id) ;
303+ VfioIdDeregistration :: LastUser
304+ }
305+ None => VfioIdDeregistration :: NotTracked ,
306+ }
307+ }
308+
309+ fn clear ( & mut self ) {
310+ self . refcounts . clear ( ) ;
311+ }
312+
313+ #[ cfg( test) ]
314+ fn remove ( & mut self , id : & str ) {
315+ self . refcounts . remove ( id) ;
316+ }
317+ }
318+
283319#[ cfg( test) ]
284320pub ( crate ) mod test_refcounts {
285- use super :: VFIO_ID_REFCOUNTS ;
321+ use super :: VFIO_ID_REGISTRY ;
286322 use std:: sync:: { Mutex , MutexGuard , PoisonError } ;
287323
288324 static VFIO_ID_REFCOUNT_TEST_LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
@@ -297,7 +333,7 @@ pub(crate) mod test_refcounts {
297333 /// Used by tests to clean up their own entries without disturbing
298334 /// parallel tests that hold refcounts for different device IDs.
299335 pub fn clear ( id : & str ) {
300- VFIO_ID_REFCOUNTS . lock ( ) . unwrap ( ) . remove ( id) ;
336+ VFIO_ID_REGISTRY . lock ( ) . unwrap ( ) . remove ( id) ;
301337 }
302338}
303339
@@ -447,6 +483,51 @@ mod tests {
447483 ) ;
448484 }
449485
486+ #[ test]
487+ fn test_deregister_by_cached_value_updates_refcount ( ) {
488+ let _refcount_guard = test_refcounts:: guard ( ) ;
489+ test_refcounts:: clear ( "10de 26ba" ) ;
490+ let ( tmp, sysfs) = setup_mock_sysfs ( ) ;
491+
492+ create_pci_device (
493+ & sysfs,
494+ tmp. path ( ) ,
495+ "0000:2d:00.0" ,
496+ "0x10de" ,
497+ "0x26ba" ,
498+ "0x030000" ,
499+ 42 ,
500+ ) ;
501+ create_pci_device (
502+ & sysfs,
503+ tmp. path ( ) ,
504+ "0000:3b:00.0" ,
505+ "0x10de" ,
506+ "0x26ba" ,
507+ "0x030200" ,
508+ 43 ,
509+ ) ;
510+ create_new_id_file ( & sysfs) ;
511+ create_remove_id_file ( & sysfs) ;
512+
513+ register_vfio_new_id ( & sysfs, "0000:2d:00.0" ) ;
514+ register_vfio_new_id ( & sysfs, "0000:3b:00.0" ) ;
515+
516+ deregister_vfio_id_by_value ( & sysfs, "10de 26ba" ) ;
517+ let written = fs:: read_to_string ( sysfs. vfio_pci_remove_id ( ) ) . unwrap ( ) ;
518+ assert_eq ! (
519+ written, "" ,
520+ "cached deregister should respect remaining refcount users"
521+ ) ;
522+
523+ deregister_vfio_new_id ( & sysfs, "0000:3b:00.0" ) ;
524+ let written = fs:: read_to_string ( sysfs. vfio_pci_remove_id ( ) ) . unwrap ( ) ;
525+ assert_eq ! (
526+ written, "10de 26ba" ,
527+ "final deregister should write remove_id after cached release decremented refcount"
528+ ) ;
529+ }
530+
450531 #[ test]
451532 fn test_bind_device_to_vfio_already_bound ( ) {
452533 let ( tmp, sysfs) = setup_mock_sysfs ( ) ;
0 commit comments