[PATCH] Notifier chain update: API changes
[linux-2.6-block.git] / kernel / sys.c
index 38bc73ede2baddef2e3c1068d26cd6e8b4e4ad24..c93d37f71aef98b23a9f43f6ac7d1f37b9ff20fd 100644 (file)
@@ -95,99 +95,304 @@ int cad_pid = 1;
  *     and the like. 
  */
 
-static struct notifier_block *reboot_notifier_list;
-static DEFINE_RWLOCK(notifier_lock);
+static BLOCKING_NOTIFIER_HEAD(reboot_notifier_list);
+
+/*
+ *     Notifier chain core routines.  The exported routines below
+ *     are layered on top of these, with appropriate locking added.
+ */
+
+static int notifier_chain_register(struct notifier_block **nl,
+               struct notifier_block *n)
+{
+       while ((*nl) != NULL) {
+               if (n->priority > (*nl)->priority)
+                       break;
+               nl = &((*nl)->next);
+       }
+       n->next = *nl;
+       rcu_assign_pointer(*nl, n);
+       return 0;
+}
+
+static int notifier_chain_unregister(struct notifier_block **nl,
+               struct notifier_block *n)
+{
+       while ((*nl) != NULL) {
+               if ((*nl) == n) {
+                       rcu_assign_pointer(*nl, n->next);
+                       return 0;
+               }
+               nl = &((*nl)->next);
+       }
+       return -ENOENT;
+}
+
+static int __kprobes notifier_call_chain(struct notifier_block **nl,
+               unsigned long val, void *v)
+{
+       int ret = NOTIFY_DONE;
+       struct notifier_block *nb;
+
+       nb = rcu_dereference(*nl);
+       while (nb) {
+               ret = nb->notifier_call(nb, val, v);
+               if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
+                       break;
+               nb = rcu_dereference(nb->next);
+       }
+       return ret;
+}
+
+/*
+ *     Atomic notifier chain routines.  Registration and unregistration
+ *     use a mutex, and call_chain is synchronized by RCU (no locks).
+ */
 
 /**
- *     notifier_chain_register - Add notifier to a notifier chain
- *     @list: Pointer to root list pointer
+ *     atomic_notifier_chain_register - Add notifier to an atomic notifier chain
+ *     @nh: Pointer to head of the atomic notifier chain
  *     @n: New entry in notifier chain
  *
- *     Adds a notifier to a notifier chain.
+ *     Adds a notifier to an atomic notifier chain.
  *
  *     Currently always returns zero.
  */
+
+int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
+               struct notifier_block *n)
+{
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&nh->lock, flags);
+       ret = notifier_chain_register(&nh->head, n);
+       spin_unlock_irqrestore(&nh->lock, flags);
+       return ret;
+}
+
+EXPORT_SYMBOL_GPL(atomic_notifier_chain_register);
+
+/**
+ *     atomic_notifier_chain_unregister - Remove notifier from an atomic notifier chain
+ *     @nh: Pointer to head of the atomic notifier chain
+ *     @n: Entry to remove from notifier chain
+ *
+ *     Removes a notifier from an atomic notifier chain.
+ *
+ *     Returns zero on success or %-ENOENT on failure.
+ */
+int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
+               struct notifier_block *n)
+{
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&nh->lock, flags);
+       ret = notifier_chain_unregister(&nh->head, n);
+       spin_unlock_irqrestore(&nh->lock, flags);
+       synchronize_rcu();
+       return ret;
+}
+
+EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister);
+
+/**
+ *     atomic_notifier_call_chain - Call functions in an atomic notifier chain
+ *     @nh: Pointer to head of the atomic notifier chain
+ *     @val: Value passed unmodified to notifier function
+ *     @v: Pointer passed unmodified to notifier function
+ *
+ *     Calls each function in a notifier chain in turn.  The functions
+ *     run in an atomic context, so they must not block.
+ *     This routine uses RCU to synchronize with changes to the chain.
+ *
+ *     If the return value of the notifier can be and'ed
+ *     with %NOTIFY_STOP_MASK then atomic_notifier_call_chain
+ *     will return immediately, with the return value of
+ *     the notifier function which halted execution.
+ *     Otherwise the return value is the return value
+ *     of the last notifier function called.
+ */
  
-int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
+int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
+               unsigned long val, void *v)
 {
-       write_lock(&notifier_lock);
-       while(*list)
-       {
-               if(n->priority > (*list)->priority)
-                       break;
-               list= &((*list)->next);
-       }
-       n->next = *list;
-       *list=n;
-       write_unlock(&notifier_lock);
-       return 0;
+       int ret;
+
+       rcu_read_lock();
+       ret = notifier_call_chain(&nh->head, val, v);
+       rcu_read_unlock();
+       return ret;
 }
 
-EXPORT_SYMBOL(notifier_chain_register);
+EXPORT_SYMBOL_GPL(atomic_notifier_call_chain);
+
+/*
+ *     Blocking notifier chain routines.  All access to the chain is
+ *     synchronized by an rwsem.
+ */
 
 /**
- *     notifier_chain_unregister - Remove notifier from a notifier chain
- *     @nl: Pointer to root list pointer
+ *     blocking_notifier_chain_register - Add notifier to a blocking notifier chain
+ *     @nh: Pointer to head of the blocking notifier chain
  *     @n: New entry in notifier chain
  *
- *     Removes a notifier from a notifier chain.
+ *     Adds a notifier to a blocking notifier chain.
+ *     Must be called in process context.
  *
- *     Returns zero on success, or %-ENOENT on failure.
+ *     Currently always returns zero.
  */
  
-int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n)
+int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
+               struct notifier_block *n)
 {
-       write_lock(&notifier_lock);
-       while((*nl)!=NULL)
-       {
-               if((*nl)==n)
-               {
-                       *nl=n->next;
-                       write_unlock(&notifier_lock);
-                       return 0;
-               }
-               nl=&((*nl)->next);
-       }
-       write_unlock(&notifier_lock);
-       return -ENOENT;
+       int ret;
+
+       /*
+        * This code gets used during boot-up, when task switching is
+        * not yet working and interrupts must remain disabled.  At
+        * such times we must not call down_write().
+        */
+       if (unlikely(system_state == SYSTEM_BOOTING))
+               return notifier_chain_register(&nh->head, n);
+
+       down_write(&nh->rwsem);
+       ret = notifier_chain_register(&nh->head, n);
+       up_write(&nh->rwsem);
+       return ret;
 }
 
-EXPORT_SYMBOL(notifier_chain_unregister);
+EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);
 
 /**
- *     notifier_call_chain - Call functions in a notifier chain
- *     @n: Pointer to root pointer of notifier chain
+ *     blocking_notifier_chain_unregister - Remove notifier from a blocking notifier chain
+ *     @nh: Pointer to head of the blocking notifier chain
+ *     @n: Entry to remove from notifier chain
+ *
+ *     Removes a notifier from a blocking notifier chain.
+ *     Must be called from process context.
+ *
+ *     Returns zero on success or %-ENOENT on failure.
+ */
+int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,
+               struct notifier_block *n)
+{
+       int ret;
+
+       /*
+        * This code gets used during boot-up, when task switching is
+        * not yet working and interrupts must remain disabled.  At
+        * such times we must not call down_write().
+        */
+       if (unlikely(system_state == SYSTEM_BOOTING))
+               return notifier_chain_unregister(&nh->head, n);
+
+       down_write(&nh->rwsem);
+       ret = notifier_chain_unregister(&nh->head, n);
+       up_write(&nh->rwsem);
+       return ret;
+}
+
+EXPORT_SYMBOL_GPL(blocking_notifier_chain_unregister);
+
+/**
+ *     blocking_notifier_call_chain - Call functions in a blocking notifier chain
+ *     @nh: Pointer to head of the blocking notifier chain
  *     @val: Value passed unmodified to notifier function
  *     @v: Pointer passed unmodified to notifier function
  *
- *     Calls each function in a notifier chain in turn.
+ *     Calls each function in a notifier chain in turn.  The functions
+ *     run in a process context, so they are allowed to block.
  *
- *     If the return value of the notifier can be and'd
- *     with %NOTIFY_STOP_MASK, then notifier_call_chain
+ *     If the return value of the notifier can be and'ed
+ *     with %NOTIFY_STOP_MASK then blocking_notifier_call_chain
  *     will return immediately, with the return value of
  *     the notifier function which halted execution.
- *     Otherwise, the return value is the return value
+ *     Otherwise the return value is the return value
  *     of the last notifier function called.
  */
  
-int __kprobes notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)
+int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
+               unsigned long val, void *v)
 {
-       int ret=NOTIFY_DONE;
-       struct notifier_block *nb = *n;
+       int ret;
 
-       while(nb)
-       {
-               ret=nb->notifier_call(nb,val,v);
-               if(ret&NOTIFY_STOP_MASK)
-               {
-                       return ret;
-               }
-               nb=nb->next;
-       }
+       down_read(&nh->rwsem);
+       ret = notifier_call_chain(&nh->head, val, v);
+       up_read(&nh->rwsem);
        return ret;
 }
 
-EXPORT_SYMBOL(notifier_call_chain);
+EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);
+
+/*
+ *     Raw notifier chain routines.  There is no protection;
+ *     the caller must provide it.  Use at your own risk!
+ */
+
+/**
+ *     raw_notifier_chain_register - Add notifier to a raw notifier chain
+ *     @nh: Pointer to head of the raw notifier chain
+ *     @n: New entry in notifier chain
+ *
+ *     Adds a notifier to a raw notifier chain.
+ *     All locking must be provided by the caller.
+ *
+ *     Currently always returns zero.
+ */
+
+int raw_notifier_chain_register(struct raw_notifier_head *nh,
+               struct notifier_block *n)
+{
+       return notifier_chain_register(&nh->head, n);
+}
+
+EXPORT_SYMBOL_GPL(raw_notifier_chain_register);
+
+/**
+ *     raw_notifier_chain_unregister - Remove notifier from a raw notifier chain
+ *     @nh: Pointer to head of the raw notifier chain
+ *     @n: Entry to remove from notifier chain
+ *
+ *     Removes a notifier from a raw notifier chain.
+ *     All locking must be provided by the caller.
+ *
+ *     Returns zero on success or %-ENOENT on failure.
+ */
+int raw_notifier_chain_unregister(struct raw_notifier_head *nh,
+               struct notifier_block *n)
+{
+       return notifier_chain_unregister(&nh->head, n);
+}
+
+EXPORT_SYMBOL_GPL(raw_notifier_chain_unregister);
+
+/**
+ *     raw_notifier_call_chain - Call functions in a raw notifier chain
+ *     @nh: Pointer to head of the raw notifier chain
+ *     @val: Value passed unmodified to notifier function
+ *     @v: Pointer passed unmodified to notifier function
+ *
+ *     Calls each function in a notifier chain in turn.  The functions
+ *     run in an undefined context.
+ *     All locking must be provided by the caller.
+ *
+ *     If the return value of the notifier can be and'ed
+ *     with %NOTIFY_STOP_MASK then raw_notifier_call_chain
+ *     will return immediately, with the return value of
+ *     the notifier function which halted execution.
+ *     Otherwise the return value is the return value
+ *     of the last notifier function called.
+ */
+
+int raw_notifier_call_chain(struct raw_notifier_head *nh,
+               unsigned long val, void *v)
+{
+       return notifier_call_chain(&nh->head, val, v);
+}
+
+EXPORT_SYMBOL_GPL(raw_notifier_call_chain);
 
 /**
  *     register_reboot_notifier - Register function to be called at reboot time
@@ -196,13 +401,13 @@ EXPORT_SYMBOL(notifier_call_chain);
  *     Registers a function with the list of functions
  *     to be called at reboot time.
  *
- *     Currently always returns zero, as notifier_chain_register
+ *     Currently always returns zero, as blocking_notifier_chain_register
  *     always returns zero.
  */
  
 int register_reboot_notifier(struct notifier_block * nb)
 {
-       return notifier_chain_register(&reboot_notifier_list, nb);
+       return blocking_notifier_chain_register(&reboot_notifier_list, nb);
 }
 
 EXPORT_SYMBOL(register_reboot_notifier);
@@ -219,7 +424,7 @@ EXPORT_SYMBOL(register_reboot_notifier);
  
 int unregister_reboot_notifier(struct notifier_block * nb)
 {
-       return notifier_chain_unregister(&reboot_notifier_list, nb);
+       return blocking_notifier_chain_unregister(&reboot_notifier_list, nb);
 }
 
 EXPORT_SYMBOL(unregister_reboot_notifier);
@@ -380,7 +585,7 @@ EXPORT_SYMBOL_GPL(emergency_restart);
 
 void kernel_restart_prepare(char *cmd)
 {
-       notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
+       blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
        system_state = SYSTEM_RESTART;
        device_shutdown();
 }
@@ -430,7 +635,7 @@ EXPORT_SYMBOL_GPL(kernel_kexec);
 
 void kernel_shutdown_prepare(enum system_states state)
 {
-       notifier_call_chain(&reboot_notifier_list,
+       blocking_notifier_call_chain(&reboot_notifier_list,
                (state == SYSTEM_HALT)?SYS_HALT:SYS_POWER_OFF, NULL);
        system_state = state;
        device_shutdown();