Print this page
OS-1988 Make ldi_ev_remove_callbacks safe to use in LDI callbacks

@@ -19,10 +19,13 @@
  * CDDL HEADER END
  */
 /*
  * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
  */
+/*
+ * Copyright (c) 2013, Joyent, Inc.  All rights reserved.
+ */
 
 /*
  * Layered driver support.
  */
 

@@ -125,10 +128,14 @@
 
 static kmutex_t                 ldi_handle_hash_lock[LH_HASH_SZ];
 static struct ldi_handle        *ldi_handle_hash[LH_HASH_SZ];
 static size_t                   ldi_handle_hash_count;
 
+/*
+ * Use of "ldi_ev_callback_list" must be protected by ldi_ev_lock()
+ * and ldi_ev_unlock().
+ */
 static struct ldi_ev_callback_list ldi_ev_callback_list;
 
 static uint32_t ldi_ev_id_pool = 0;
 
 struct ldi_ev_cookie {

@@ -164,10 +171,12 @@
          */
         mutex_init(&ldi_ev_callback_list.le_lock, NULL, MUTEX_DEFAULT, NULL);
         cv_init(&ldi_ev_callback_list.le_cv, NULL, CV_DEFAULT, NULL);
         ldi_ev_callback_list.le_busy = 0;
         ldi_ev_callback_list.le_thread = NULL;
+        ldi_ev_callback_list.le_walker_next = NULL;
+        ldi_ev_callback_list.le_walker_prev = NULL;
         list_create(&ldi_ev_callback_list.le_head,
             sizeof (ldi_ev_callback_impl_t),
             offsetof(ldi_ev_callback_impl_t, lec_list));
 }
 

@@ -3327,12 +3336,16 @@
         LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): entered: dip=%p, ev=%s",
             (void *)dip, event));
 
         ret = LDI_EV_NONE;
         ldi_ev_lock();
+
+        VERIFY(ldi_ev_callback_list.le_walker_next == NULL);
         listp = &ldi_ev_callback_list.le_head;
-        for (lecp = list_head(listp); lecp; lecp = list_next(listp, lecp)) {
+        for (lecp = list_head(listp); lecp; lecp =
+            ldi_ev_callback_list.le_walker_next) {
+                ldi_ev_callback_list.le_walker_next = list_next(listp, lecp);
 
                 /* Check if matching device */
                 if (!ldi_ev_device_match(lecp, dip, dev, spec_type))
                         continue;
 

@@ -3384,11 +3397,13 @@
 
         /*
          * Undo notifies already sent
          */
         lecp = list_prev(listp, lecp);
-        for (; lecp; lecp = list_prev(listp, lecp)) {
+        VERIFY(ldi_ev_callback_list.le_walker_prev == NULL);
+        for (; lecp; lecp = ldi_ev_callback_list.le_walker_prev) {
+                ldi_ev_callback_list.le_walker_prev = list_prev(listp, lecp);
 
                 /*
                  * Check if matching device
                  */
                 if (!ldi_ev_device_match(lecp, dip, dev, spec_type))

@@ -3435,10 +3450,12 @@
                         lecp->lec_finalize = NULL;
                 }
         }
 
 out:
+        ldi_ev_callback_list.le_walker_next = NULL;
+        ldi_ev_callback_list.le_walker_prev = NULL;
         ldi_ev_unlock();
 
         if (ret == LDI_EV_NONE) {
                 LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): no matching "
                     "LDI callbacks"));

@@ -3550,12 +3567,15 @@
 
         LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): entered: dip=%p, result=%d"
             " event=%s", (void *)dip, ldi_result, event));
 
         ldi_ev_lock();
+        VERIFY(ldi_ev_callback_list.le_walker_next == NULL);
         listp = &ldi_ev_callback_list.le_head;
-        for (lecp = list_head(listp); lecp; lecp = list_next(listp, lecp)) {
+        for (lecp = list_head(listp); lecp; lecp =
+            ldi_ev_callback_list.le_walker_next) {
+                ldi_ev_callback_list.le_walker_next = list_next(listp, lecp);
 
                 if (lecp->lec_finalize == NULL) {
                         LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): No "
                             "finalize. Skipping"));
                         continue;       /* Not interested in finalize */

@@ -3602,10 +3622,11 @@
                             "ldi_invoke_finalize(): NULLing finalize after "
                             "calling 1 finalize following ldi_close"));
                         lecp->lec_finalize = NULL;
                 }
         }
+        ldi_ev_callback_list.le_walker_next = NULL;
         ldi_ev_unlock();
 
         if (found)
                 return;
 

@@ -3682,11 +3703,27 @@
         listp = &ldi_ev_callback_list.le_head;
         next = found = NULL;
         for (lecp = list_head(listp); lecp; lecp = next) {
                 next = list_next(listp, lecp);
                 if (lecp->lec_id == id) {
-                        ASSERT(found == NULL);
+                        VERIFY(found == NULL);
+
+                        /*
+                         * If there is a walk in progress, shift that walk
+                         * along to the next element so that we can remove
+                         * this one.  This allows us to unregister an arbitrary
+                         * number of callbacks from within a callback.
+                         *
+                         * See the struct definition (in sunldi_impl.h) for
+                         * more information.
+                         */
+                        if (ldi_ev_callback_list.le_walker_next == lecp)
+                                ldi_ev_callback_list.le_walker_next = next;
+                        if (ldi_ev_callback_list.le_walker_prev == lecp)
+                                ldi_ev_callback_list.le_walker_prev = list_prev(
+                                    listp, ldi_ev_callback_list.le_walker_prev);
+
                         list_remove(listp, lecp);
                         found = lecp;
                 }
         }
         ldi_ev_unlock();