]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ipmi: Rework locking and shutdown for hot remove
authorCorey Minyard <cminyard@mvista.com>
Fri, 6 Apr 2018 03:05:04 +0000 (22:05 -0500)
committerCorey Minyard <cminyard@mvista.com>
Wed, 18 Apr 2018 15:22:58 +0000 (10:22 -0500)
To handle hot remove of interfaces, a lot of rework had to be
done to the locking.  Several things were switched over to srcu
and shutdown for users and interfaces was added for cleaner
shutdown.

Signed-off-by: Corey Minyard <cminyard@mvista.com>
drivers/char/ipmi/ipmi_msghandler.c

index c21c4e021dab001b454e28fcb52b5b1b316ac70a..a27b50ac2b7f356e3ca813249354be73c481a653 100644 (file)
@@ -197,8 +197,12 @@ MODULE_PARM_DESC(default_max_retries,
 struct ipmi_user {
        struct list_head link;
 
-       /* Set to false when the user is destroyed. */
-       bool valid;
+       /*
+        * Set to NULL when the user is destroyed, a pointer to myself
+        * so srcu_dereference can be used on it.
+        */
+       struct ipmi_user *self;
+       struct srcu_struct release_barrier;
 
        struct kref refcount;
 
@@ -213,6 +217,23 @@ struct ipmi_user {
        bool gets_events;
 };
 
+static struct ipmi_user *acquire_ipmi_user(struct ipmi_user *user, int *index)
+       __acquires(user->release_barrier)
+{
+       struct ipmi_user *ruser;
+
+       *index = srcu_read_lock(&user->release_barrier);
+       ruser = srcu_dereference(user->self, &user->release_barrier);
+       if (!ruser)
+               srcu_read_unlock(&user->release_barrier, *index);
+       return ruser;
+}
+
+static void release_ipmi_user(struct ipmi_user *user, int index)
+{
+       srcu_read_unlock(&user->release_barrier, index);
+}
+
 struct cmd_rcvr {
        struct list_head link;
 
@@ -444,10 +465,11 @@ struct ipmi_smi {
        struct list_head link;
 
        /*
-        * The list of upper layers that are using me.  seq_lock
-        * protects this.
+        * The list of upper layers that are using me.  seq_lock write
+        * protects this.  Read protection is with srcu.
         */
        struct list_head users;
+       struct srcu_struct users_srcu;
 
        /* Used for wake ups at startup. */
        wait_queue_head_t waitq;
@@ -467,12 +489,6 @@ struct ipmi_smi {
        bool in_bmc_register;  /* Handle recursive situations.  Yuck. */
        struct work_struct bmc_reg_work;
 
-       /*
-        * This is the lower-layer's sender routine.  Note that you
-        * must either be holding the ipmi_interfaces_mutex or be in
-        * an umpreemptible region to use this.  You must fetch the
-        * value into a local variable and make sure it is not NULL.
-        */
        const struct ipmi_smi_handlers *handlers;
        void                     *send_info;
 
@@ -615,6 +631,7 @@ static DEFINE_MUTEX(ipmidriver_mutex);
 
 static LIST_HEAD(ipmi_interfaces);
 static DEFINE_MUTEX(ipmi_interfaces_mutex);
+DEFINE_STATIC_SRCU(ipmi_interfaces_srcu);
 
 /*
  * List of watchers that want to know when smi's are added and deleted.
@@ -715,58 +732,32 @@ static void intf_free(struct kref *ref)
 
 struct watcher_entry {
        int              intf_num;
-       struct ipmi_smi *intf;
+       struct ipmi_smi  *intf;
        struct list_head link;
 };
 
 int ipmi_smi_watcher_register(struct ipmi_smi_watcher *watcher)
 {
        struct ipmi_smi *intf;
-       LIST_HEAD(to_deliver);
-       struct watcher_entry *e, *e2;
+       int index;
 
        mutex_lock(&smi_watchers_mutex);
 
-       mutex_lock(&ipmi_interfaces_mutex);
-
-       /* Build a list of things to deliver. */
-       list_for_each_entry(intf, &ipmi_interfaces, link) {
-               if (intf->intf_num == -1)
-                       continue;
-               e = kmalloc(sizeof(*e), GFP_KERNEL);
-               if (!e)
-                       goto out_err;
-               kref_get(&intf->refcount);
-               e->intf = intf;
-               e->intf_num = intf->intf_num;
-               list_add_tail(&e->link, &to_deliver);
-       }
-
-       /* We will succeed, so add it to the list. */
        list_add(&watcher->link, &smi_watchers);
 
-       mutex_unlock(&ipmi_interfaces_mutex);
+       index = srcu_read_lock(&ipmi_interfaces_srcu);
+       list_for_each_entry_rcu(intf, &ipmi_interfaces, link) {
+               int intf_num = READ_ONCE(intf->intf_num);
 
-       list_for_each_entry_safe(e, e2, &to_deliver, link) {
-               list_del(&e->link);
-               watcher->new_smi(e->intf_num, e->intf->si_dev);
-               kref_put(&e->intf->refcount, intf_free);
-               kfree(e);
+               if (intf_num == -1)
+                       continue;
+               watcher->new_smi(intf_num, intf->si_dev);
        }
+       srcu_read_unlock(&ipmi_interfaces_srcu, index);
 
        mutex_unlock(&smi_watchers_mutex);
 
        return 0;
-
- out_err:
-       mutex_unlock(&ipmi_interfaces_mutex);
-       mutex_unlock(&smi_watchers_mutex);
-       list_for_each_entry_safe(e, e2, &to_deliver, link) {
-               list_del(&e->link);
-               kref_put(&e->intf->refcount, intf_free);
-               kfree(e);
-       }
-       return -ENOMEM;
 }
 EXPORT_SYMBOL(ipmi_smi_watcher_register);
 
@@ -787,12 +778,14 @@ call_smi_watchers(int i, struct device *dev)
 {
        struct ipmi_smi_watcher *w;
 
+       mutex_lock(&smi_watchers_mutex);
        list_for_each_entry(w, &smi_watchers, link) {
                if (try_module_get(w->owner)) {
                        w->new_smi(i, dev);
                        module_put(w->owner);
                }
        }
+       mutex_unlock(&smi_watchers_mutex);
 }
 
 static int
@@ -905,9 +898,17 @@ static int deliver_response(struct ipmi_smi *intf, struct ipmi_recv_msg *msg)
                 * receive handler doesn't much meaning and has a deadlock
                 * risk.  At this moment, simply skip it in that case.
                 */
+               int index;
+               struct ipmi_user *user = acquire_ipmi_user(msg->user, &index);
 
-               struct ipmi_user *user = msg->user;
-               user->handler->ipmi_recv_hndl(msg, user->handler_data);
+               if (user) {
+                       user->handler->ipmi_recv_hndl(msg, user->handler_data);
+                       release_ipmi_user(msg->user, index);
+               } else {
+                       /* User went away, give up. */
+                       ipmi_free_recv_msg(msg);
+                       rv = -EINVAL;
+               }
        }
 
        return rv;
@@ -1094,7 +1095,7 @@ int ipmi_create_user(unsigned int          if_num,
 {
        unsigned long flags;
        struct ipmi_user *new_user;
-       int           rv = 0;
+       int           rv = 0, index;
        struct ipmi_smi *intf;
 
        /*
@@ -1129,7 +1130,7 @@ int ipmi_create_user(unsigned int          if_num,
        if (!new_user)
                return -ENOMEM;
 
-       mutex_lock(&ipmi_interfaces_mutex);
+       index = srcu_read_lock(&ipmi_interfaces_srcu);
        list_for_each_entry_rcu(intf, &ipmi_interfaces, link) {
                if (intf->intf_num == if_num)
                        goto found;
@@ -1139,6 +1140,10 @@ int ipmi_create_user(unsigned int          if_num,
        goto out_kfree;
 
  found:
+       rv = init_srcu_struct(&new_user->release_barrier);
+       if (rv)
+               goto out_kfree;
+
        /* Note that each existing user holds a refcount to the interface. */
        kref_get(&intf->refcount);
 
@@ -1148,26 +1153,7 @@ int ipmi_create_user(unsigned int          if_num,
        new_user->intf = intf;
        new_user->gets_events = false;
 
-       if (!try_module_get(intf->handlers->owner)) {
-               rv = -ENODEV;
-               goto out_kref;
-       }
-
-       if (intf->handlers->inc_usecount) {
-               rv = intf->handlers->inc_usecount(intf->send_info);
-               if (rv) {
-                       module_put(intf->handlers->owner);
-                       goto out_kref;
-               }
-       }
-
-       /*
-        * Hold the lock so intf->handlers is guaranteed to be good
-        * until now
-        */
-       mutex_unlock(&ipmi_interfaces_mutex);
-
-       new_user->valid = true;
+       rcu_assign_pointer(new_user->self, new_user);
        spin_lock_irqsave(&intf->seq_lock, flags);
        list_add_rcu(&new_user->link, &intf->users);
        spin_unlock_irqrestore(&intf->seq_lock, flags);
@@ -1176,13 +1162,12 @@ int ipmi_create_user(unsigned int          if_num,
                if (atomic_inc_return(&intf->event_waiters) == 1)
                        need_waiter(intf);
        }
+       srcu_read_unlock(&ipmi_interfaces_srcu, index);
        *user = new_user;
        return 0;
 
-out_kref:
-       kref_put(&intf->refcount, intf_free);
 out_kfree:
-       mutex_unlock(&ipmi_interfaces_mutex);
+       srcu_read_unlock(&ipmi_interfaces_srcu, index);
        kfree(new_user);
        return rv;
 }
@@ -1190,26 +1175,25 @@ EXPORT_SYMBOL(ipmi_create_user);
 
 int ipmi_get_smi_info(int if_num, struct ipmi_smi_info *data)
 {
-       int           rv = 0;
+       int rv, index;
        struct ipmi_smi *intf;
-       const struct ipmi_smi_handlers *handlers;
 
-       mutex_lock(&ipmi_interfaces_mutex);
+       index = srcu_read_lock(&ipmi_interfaces_srcu);
        list_for_each_entry_rcu(intf, &ipmi_interfaces, link) {
                if (intf->intf_num == if_num)
                        goto found;
        }
+       srcu_read_unlock(&ipmi_interfaces_srcu, index);
+
        /* Not found, return an error */
-       rv = -EINVAL;
-       mutex_unlock(&ipmi_interfaces_mutex);
-       return rv;
+       return -EINVAL;
 
 found:
-       handlers = intf->handlers;
-       rv = -ENOSYS;
-       if (handlers->get_smi_info)
-               rv = handlers->get_smi_info(intf->send_info, data);
-       mutex_unlock(&ipmi_interfaces_mutex);
+       if (!intf->handlers->get_smi_info)
+               rv = -ENOTTY;
+       else
+               rv = intf->handlers->get_smi_info(intf->send_info, data);
+       srcu_read_unlock(&ipmi_interfaces_srcu, index);
 
        return rv;
 }
@@ -1221,7 +1205,7 @@ static void free_user(struct kref *ref)
        kfree(user);
 }
 
-int ipmi_destroy_user(struct ipmi_user *user)
+static void _ipmi_destroy_user(struct ipmi_user *user)
 {
        struct ipmi_smi  *intf = user->intf;
        int              i;
@@ -1229,7 +1213,22 @@ int ipmi_destroy_user(struct ipmi_user *user)
        struct cmd_rcvr  *rcvr;
        struct cmd_rcvr  *rcvrs = NULL;
 
-       user->valid = false;
+       if (!acquire_ipmi_user(user, &i)) {
+               /*
+                * The user has already been cleaned up, just make sure
+                * nothing is using it and return.
+                */
+               synchronize_srcu(&user->release_barrier);
+               return;
+       }
+
+       rcu_assign_pointer(user->self, NULL);
+       release_ipmi_user(user, i);
+
+       synchronize_srcu(&user->release_barrier);
+
+       if (user->handler->shutdown)
+               user->handler->shutdown(user->handler_data);
 
        if (user->handler->ipmi_watchdog_pretimeout)
                atomic_dec(&intf->event_waiters);
@@ -1254,7 +1253,7 @@ int ipmi_destroy_user(struct ipmi_user *user)
         * Remove the user from the command receiver's table.  First
         * we build a list of everything (not using the standard link,
         * since other things may be using it till we do
-        * synchronize_rcu()) then free everything in that list.
+        * synchronize_srcu()) then free everything in that list.
         */
        mutex_lock(&intf->cmd_rcvrs_mutex);
        list_for_each_entry_rcu(rcvr, &intf->cmd_rcvrs, link) {
@@ -1272,16 +1271,14 @@ int ipmi_destroy_user(struct ipmi_user *user)
                kfree(rcvr);
        }
 
-       mutex_lock(&ipmi_interfaces_mutex);
-       if (intf->handlers) {
-               module_put(intf->handlers->owner);
-               if (intf->handlers->dec_usecount)
-                       intf->handlers->dec_usecount(intf->send_info);
-       }
-       mutex_unlock(&ipmi_interfaces_mutex);
-
        kref_put(&intf->refcount, intf_free);
+}
+
+int ipmi_destroy_user(struct ipmi_user *user)
+{
+       _ipmi_destroy_user(user);
 
+       cleanup_srcu_struct(&user->release_barrier);
        kref_put(&user->refcount, free_user);
 
        return 0;
@@ -1293,16 +1290,20 @@ int ipmi_get_version(struct ipmi_user *user,
                     unsigned char *minor)
 {
        struct ipmi_device_id id;
-       int rv;
+       int rv, index;
 
-       rv = bmc_get_device_id(user->intf, NULL, &id, NULL, NULL);
-       if (rv)
-               return rv;
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
 
-       *major = ipmi_version_major(&id);
-       *minor = ipmi_version_minor(&id);
+       rv = bmc_get_device_id(user->intf, NULL, &id, NULL, NULL);
+       if (!rv) {
+               *major = ipmi_version_major(&id);
+               *minor = ipmi_version_minor(&id);
+       }
+       release_ipmi_user(user, index);
 
-       return 0;
+       return rv;
 }
 EXPORT_SYMBOL(ipmi_get_version);
 
@@ -1310,9 +1311,17 @@ int ipmi_set_my_address(struct ipmi_user *user,
                        unsigned int  channel,
                        unsigned char address)
 {
+       int index;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        if (channel >= IPMI_MAX_CHANNELS)
                return -EINVAL;
        user->intf->addrinfo[channel].address = address;
+       release_ipmi_user(user, index);
+
        return 0;
 }
 EXPORT_SYMBOL(ipmi_set_my_address);
@@ -1321,9 +1330,17 @@ int ipmi_get_my_address(struct ipmi_user *user,
                        unsigned int  channel,
                        unsigned char *address)
 {
+       int index;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        if (channel >= IPMI_MAX_CHANNELS)
                return -EINVAL;
        *address = user->intf->addrinfo[channel].address;
+       release_ipmi_user(user, index);
+
        return 0;
 }
 EXPORT_SYMBOL(ipmi_get_my_address);
@@ -1332,9 +1349,17 @@ int ipmi_set_my_LUN(struct ipmi_user *user,
                    unsigned int  channel,
                    unsigned char LUN)
 {
+       int index;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        if (channel >= IPMI_MAX_CHANNELS)
                return -EINVAL;
        user->intf->addrinfo[channel].lun = LUN & 0x3;
+       release_ipmi_user(user, index);
+
        return 0;
 }
 EXPORT_SYMBOL(ipmi_set_my_LUN);
@@ -1343,21 +1368,34 @@ int ipmi_get_my_LUN(struct ipmi_user *user,
                    unsigned int  channel,
                    unsigned char *address)
 {
+       int index;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        if (channel >= IPMI_MAX_CHANNELS)
                return -EINVAL;
        *address = user->intf->addrinfo[channel].lun;
+       release_ipmi_user(user, index);
+
        return 0;
 }
 EXPORT_SYMBOL(ipmi_get_my_LUN);
 
 int ipmi_get_maintenance_mode(struct ipmi_user *user)
 {
-       int           mode;
+       int mode, index;
        unsigned long flags;
 
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        spin_lock_irqsave(&user->intf->maintenance_mode_lock, flags);
        mode = user->intf->maintenance_mode;
        spin_unlock_irqrestore(&user->intf->maintenance_mode_lock, flags);
+       release_ipmi_user(user, index);
 
        return mode;
 }
@@ -1372,10 +1410,14 @@ static void maintenance_mode_update(struct ipmi_smi *intf)
 
 int ipmi_set_maintenance_mode(struct ipmi_user *user, int mode)
 {
-       int           rv = 0;
+       int rv = 0, index;
        unsigned long flags;
        struct ipmi_smi *intf = user->intf;
 
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        spin_lock_irqsave(&intf->maintenance_mode_lock, flags);
        if (intf->maintenance_mode != mode) {
                switch (mode) {
@@ -1402,6 +1444,7 @@ int ipmi_set_maintenance_mode(struct ipmi_user *user, int mode)
        }
  out_unlock:
        spin_unlock_irqrestore(&intf->maintenance_mode_lock, flags);
+       release_ipmi_user(user, index);
 
        return rv;
 }
@@ -1413,6 +1456,11 @@ int ipmi_set_gets_events(struct ipmi_user *user, bool val)
        struct ipmi_smi      *intf = user->intf;
        struct ipmi_recv_msg *msg, *msg2;
        struct list_head     msgs;
+       int index;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
 
        INIT_LIST_HEAD(&msgs);
 
@@ -1462,6 +1510,7 @@ int ipmi_set_gets_events(struct ipmi_user *user, bool val)
 
  out:
        spin_unlock_irqrestore(&intf->events_lock, flags);
+       release_ipmi_user(user, index);
 
        return 0;
 }
@@ -1504,8 +1553,11 @@ int ipmi_register_for_cmd(struct ipmi_user *user,
 {
        struct ipmi_smi *intf = user->intf;
        struct cmd_rcvr *rcvr;
-       int             rv = 0;
+       int rv = 0, index;
 
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
 
        rcvr = kmalloc(sizeof(*rcvr), GFP_KERNEL);
        if (!rcvr)
@@ -1531,6 +1583,7 @@ int ipmi_register_for_cmd(struct ipmi_user *user,
        mutex_unlock(&intf->cmd_rcvrs_mutex);
        if (rv)
                kfree(rcvr);
+       release_ipmi_user(user, index);
 
        return rv;
 }
@@ -1544,7 +1597,11 @@ int ipmi_unregister_for_cmd(struct ipmi_user *user,
        struct ipmi_smi *intf = user->intf;
        struct cmd_rcvr *rcvr;
        struct cmd_rcvr *rcvrs = NULL;
-       int i, rv = -ENOENT;
+       int i, rv = -ENOENT, index;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
 
        mutex_lock(&intf->cmd_rcvrs_mutex);
        for (i = 0; i < IPMI_NUM_CHANNELS; i++) {
@@ -1565,12 +1622,14 @@ int ipmi_unregister_for_cmd(struct ipmi_user *user,
        }
        mutex_unlock(&intf->cmd_rcvrs_mutex);
        synchronize_rcu();
+       release_ipmi_user(user, index);
        while (rcvrs) {
                atomic_dec(&intf->event_waiters);
                rcvr = rcvrs;
                rcvrs = rcvr->next;
                kfree(rcvr);
        }
+
        return rv;
 }
 EXPORT_SYMBOL(ipmi_unregister_for_cmd);
@@ -2065,8 +2124,10 @@ static int i_ipmi_request(struct ipmi_user     *user,
                recv_msg = supplied_recv;
        else {
                recv_msg = ipmi_alloc_recv_msg();
-               if (recv_msg == NULL)
-                       return -ENOMEM;
+               if (recv_msg == NULL) {
+                       rv = -ENOMEM;
+                       goto out;
+               }
        }
        recv_msg->user_msg_data = user_msg_data;
 
@@ -2076,7 +2137,8 @@ static int i_ipmi_request(struct ipmi_user     *user,
                smi_msg = ipmi_alloc_smi_msg();
                if (smi_msg == NULL) {
                        ipmi_free_recv_msg(recv_msg);
-                       return -ENOMEM;
+                       rv = -ENOMEM;
+                       goto out;
                }
        }
 
@@ -2088,6 +2150,7 @@ static int i_ipmi_request(struct ipmi_user     *user,
 
        recv_msg->user = user;
        if (user)
+               /* The put happens when the message is freed. */
                kref_get(&user->refcount);
        recv_msg->msgid = msgid;
        /*
@@ -2123,6 +2186,7 @@ out_err:
        }
        rcu_read_unlock();
 
+out:
        return rv;
 }
 
@@ -2148,25 +2212,32 @@ int ipmi_request_settime(struct ipmi_user *user,
                         unsigned int     retry_time_ms)
 {
        unsigned char saddr = 0, lun = 0;
-       int           rv;
+       int rv, index;
 
        if (!user)
                return -EINVAL;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        rv = check_addr(user->intf, addr, &saddr, &lun);
-       if (rv)
-               return rv;
-       return i_ipmi_request(user,
-                             user->intf,
-                             addr,
-                             msgid,
-                             msg,
-                             user_msg_data,
-                             NULL, NULL,
-                             priority,
-                             saddr,
-                             lun,
-                             retries,
-                             retry_time_ms);
+       if (!rv)
+               rv = i_ipmi_request(user,
+                                   user->intf,
+                                   addr,
+                                   msgid,
+                                   msg,
+                                   user_msg_data,
+                                   NULL, NULL,
+                                   priority,
+                                   saddr,
+                                   lun,
+                                   retries,
+                                   retry_time_ms);
+
+       release_ipmi_user(user, index);
+       return rv;
 }
 EXPORT_SYMBOL(ipmi_request_settime);
 
@@ -2180,25 +2251,32 @@ int ipmi_request_supply_msgs(struct ipmi_user     *user,
                             int                  priority)
 {
        unsigned char saddr = 0, lun = 0;
-       int           rv;
+       int rv, index;
 
        if (!user)
                return -EINVAL;
+
+       user = acquire_ipmi_user(user, &index);
+       if (!user)
+               return -ENODEV;
+
        rv = check_addr(user->intf, addr, &saddr, &lun);
-       if (rv)
-               return rv;
-       return i_ipmi_request(user,
-                             user->intf,
-                             addr,
-                             msgid,
-                             msg,
-                             user_msg_data,
-                             supplied_smi,
-                             supplied_recv,
-                             priority,
-                             saddr,
-                             lun,
-                             -1, 0);
+       if (!rv)
+               rv = i_ipmi_request(user,
+                                   user->intf,
+                                   addr,
+                                   msgid,
+                                   msg,
+                                   user_msg_data,
+                                   supplied_smi,
+                                   supplied_recv,
+                                   priority,
+                                   saddr,
+                                   lun,
+                                   -1, 0);
+
+       release_ipmi_user(user, index);
+       return rv;
 }
 EXPORT_SYMBOL(ipmi_request_supply_msgs);
 
@@ -3455,6 +3533,13 @@ int ipmi_register_smi(const struct ipmi_smi_handlers *handlers,
        if (!intf)
                return -ENOMEM;
 
+       rv = init_srcu_struct(&intf->users_srcu);
+       if (rv) {
+               kfree(intf);
+               return rv;
+       }
+
+
        intf->bmc = &intf->tmp_bmc;
        INIT_LIST_HEAD(&intf->bmc->intfs);
        mutex_init(&intf->bmc->dyn_mutex);
@@ -3507,7 +3592,6 @@ int ipmi_register_smi(const struct ipmi_smi_handlers *handlers,
        intf->proc_dir = NULL;
 #endif
 
-       mutex_lock(&smi_watchers_mutex);
        mutex_lock(&ipmi_interfaces_mutex);
        /* Look for a hole in the numbers. */
        i = 0;
@@ -3552,11 +3636,10 @@ int ipmi_register_smi(const struct ipmi_smi_handlers *handlers,
                if (intf->proc_dir)
                        remove_proc_entries(intf);
 #endif
-               intf->handlers = NULL;
                list_del_rcu(&intf->link);
                mutex_unlock(&ipmi_interfaces_mutex);
-               mutex_unlock(&smi_watchers_mutex);
-               synchronize_rcu();
+               synchronize_srcu(&ipmi_interfaces_srcu);
+               cleanup_srcu_struct(&intf->users_srcu);
                kref_put(&intf->refcount, intf_free);
        } else {
                /*
@@ -3567,9 +3650,9 @@ int ipmi_register_smi(const struct ipmi_smi_handlers *handlers,
                smp_wmb();
                intf->intf_num = i;
                mutex_unlock(&ipmi_interfaces_mutex);
+
                /* After this point the interface is legal to use. */
                call_smi_watchers(i, intf->si_dev);
-               mutex_unlock(&smi_watchers_mutex);
        }
 
        return rv;
@@ -3631,45 +3714,49 @@ static void cleanup_smi_msgs(struct ipmi_smi *intf)
 int ipmi_unregister_smi(struct ipmi_smi *intf)
 {
        struct ipmi_smi_watcher *w;
-       int intf_num = intf->intf_num;
-       struct ipmi_user *user;
+       int intf_num = intf->intf_num, index;
 
-       mutex_lock(&smi_watchers_mutex);
        mutex_lock(&ipmi_interfaces_mutex);
        intf->intf_num = -1;
        intf->in_shutdown = true;
        list_del_rcu(&intf->link);
        mutex_unlock(&ipmi_interfaces_mutex);
-       synchronize_rcu();
-
-       cleanup_smi_msgs(intf);
-
-       /* Clean up the effects of users on the lower-level software. */
-       mutex_lock(&ipmi_interfaces_mutex);
-       rcu_read_lock();
-       list_for_each_entry_rcu(user, &intf->users, link) {
-               module_put(intf->handlers->owner);
-               if (intf->handlers->dec_usecount)
-                       intf->handlers->dec_usecount(intf->send_info);
-       }
-       rcu_read_unlock();
-       intf->handlers = NULL;
-       mutex_unlock(&ipmi_interfaces_mutex);
+       synchronize_srcu(&ipmi_interfaces_srcu);
 
-#ifdef CONFIG_IPMI_PROC_INTERFACE
-       remove_proc_entries(intf);
-#endif
-       ipmi_bmc_unregister(intf);
+       /* At this point no users can be added to the interface. */
 
        /*
         * Call all the watcher interfaces to tell them that
-        * an interface is gone.
+        * an interface is going away.
         */
+       mutex_lock(&smi_watchers_mutex);
        list_for_each_entry(w, &smi_watchers, link)
                w->smi_gone(intf_num);
        mutex_unlock(&smi_watchers_mutex);
 
+       index = srcu_read_lock(&intf->users_srcu);
+       while (!list_empty(&intf->users)) {
+               struct ipmi_user *user =
+                       container_of(list_next_rcu(&intf->users),
+                                    struct ipmi_user, link);
+
+               _ipmi_destroy_user(user);
+       }
+       srcu_read_unlock(&intf->users_srcu, index);
+
+       if (intf->handlers->shutdown)
+               intf->handlers->shutdown(intf->send_info);
+
+       cleanup_smi_msgs(intf);
+
+#ifdef CONFIG_IPMI_PROC_INTERFACE
+       remove_proc_entries(intf);
+#endif
+       ipmi_bmc_unregister(intf);
+
+       cleanup_srcu_struct(&intf->users_srcu);
        kref_put(&intf->refcount, intf_free);
+
        return 0;
 }
 EXPORT_SYMBOL(ipmi_unregister_smi);
@@ -4141,8 +4228,7 @@ static int handle_read_event_rsp(struct ipmi_smi *intf,
        struct ipmi_recv_msg *recv_msg, *recv_msg2;
        struct list_head     msgs;
        struct ipmi_user     *user;
-       int                  rv = 0;
-       int                  deliver_count = 0;
+       int rv = 0, deliver_count = 0, index;
        unsigned long        flags;
 
        if (msg->rsp_size < 19) {
@@ -4166,7 +4252,7 @@ static int handle_read_event_rsp(struct ipmi_smi *intf,
         * Allocate and fill in one message for every user that is
         * getting events.
         */
-       rcu_read_lock();
+       index = srcu_read_lock(&intf->users_srcu);
        list_for_each_entry_rcu(user, &intf->users, link) {
                if (!user->gets_events)
                        continue;
@@ -4195,7 +4281,7 @@ static int handle_read_event_rsp(struct ipmi_smi *intf,
                kref_get(&user->refcount);
                list_add_tail(&recv_msg->link, &msgs);
        }
-       rcu_read_unlock();
+       srcu_read_unlock(&intf->users_srcu, index);
 
        if (deliver_count) {
                /* Now deliver all the messages. */
@@ -4242,7 +4328,7 @@ static int handle_bmc_rsp(struct ipmi_smi *intf,
                          struct ipmi_smi_msg *msg)
 {
        struct ipmi_recv_msg *recv_msg;
-       struct ipmi_user     *user;
+       struct ipmi_system_interface_addr *smi_addr;
 
        recv_msg = (struct ipmi_recv_msg *) msg->user_data;
        if (recv_msg == NULL) {
@@ -4251,30 +4337,19 @@ static int handle_bmc_rsp(struct ipmi_smi *intf,
                return 0;
        }
 
-       user = recv_msg->user;
-       /* Make sure the user still exists. */
-       if (user && !user->valid) {
-               /* The user for the message went away, so give up. */
-               ipmi_inc_stat(intf, unhandled_local_responses);
-               ipmi_free_recv_msg(recv_msg);
-       } else {
-               struct ipmi_system_interface_addr *smi_addr;
-
-               ipmi_inc_stat(intf, handled_local_responses);
-               recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
-               recv_msg->msgid = msg->msgid;
-               smi_addr = ((struct ipmi_system_interface_addr *)
-                           &recv_msg->addr);
-               smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
-               smi_addr->channel = IPMI_BMC_CHANNEL;
-               smi_addr->lun = msg->rsp[0] & 3;
-               recv_msg->msg.netfn = msg->rsp[0] >> 2;
-               recv_msg->msg.cmd = msg->rsp[1];
-               memcpy(recv_msg->msg_data, &msg->rsp[2], msg->rsp_size - 2);
-               recv_msg->msg.data = recv_msg->msg_data;
-               recv_msg->msg.data_len = msg->rsp_size - 2;
-               deliver_local_response(intf, recv_msg);
-       }
+       recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+       recv_msg->msgid = msg->msgid;
+       smi_addr = ((struct ipmi_system_interface_addr *)
+                   &recv_msg->addr);
+       smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+       smi_addr->channel = IPMI_BMC_CHANNEL;
+       smi_addr->lun = msg->rsp[0] & 3;
+       recv_msg->msg.netfn = msg->rsp[0] >> 2;
+       recv_msg->msg.cmd = msg->rsp[1];
+       memcpy(recv_msg->msg_data, &msg->rsp[2], msg->rsp_size - 2);
+       recv_msg->msg.data = recv_msg->msg_data;
+       recv_msg->msg.data_len = msg->rsp_size - 2;
+       deliver_local_response(intf, recv_msg);
 
        return 0;
 }
@@ -4327,7 +4402,7 @@ static int handle_one_recv_msg(struct ipmi_smi *intf,
                 * It's a response to a response we sent.  For this we
                 * deliver a send message response to the user.
                 */
-               struct ipmi_recv_msg     *recv_msg = msg->user_data;
+               struct ipmi_recv_msg *recv_msg = msg->user_data;
 
                requeue = 0;
                if (msg->rsp_size < 2)
@@ -4342,10 +4417,6 @@ static int handle_one_recv_msg(struct ipmi_smi *intf,
                if (!recv_msg)
                        goto out;
 
-               /* Make sure the user still exists. */
-               if (!recv_msg->user || !recv_msg->user->valid)
-                       goto out;
-
                recv_msg->recv_type = IPMI_RESPONSE_RESPONSE_TYPE;
                recv_msg->msg.data = recv_msg->msg_data;
                recv_msg->msg.data_len = 1;
@@ -4488,14 +4559,15 @@ static void handle_new_recv_msgs(struct ipmi_smi *intf)
         */
        if (atomic_add_unless(&intf->watchdog_pretimeouts_to_deliver, -1, 0)) {
                struct ipmi_user *user;
+               int index;
 
-               rcu_read_lock();
+               index = srcu_read_lock(&intf->users_srcu);
                list_for_each_entry_rcu(user, &intf->users, link) {
                        if (user->handler->ipmi_watchdog_pretimeout)
                                user->handler->ipmi_watchdog_pretimeout(
                                        user->handler_data);
                }
-               rcu_read_unlock();
+               srcu_read_unlock(&intf->users_srcu, index);
        }
 }
 
@@ -4662,8 +4734,7 @@ static void check_msg_timeout(struct ipmi_smi *intf, struct seq_table *ent,
                              int slot, unsigned long *flags,
                              unsigned int *waiting_msgs)
 {
-       struct ipmi_recv_msg     *msg;
-       const struct ipmi_smi_handlers *handlers;
+       struct ipmi_recv_msg *msg;
 
        if (intf->in_shutdown)
                return;
@@ -4721,8 +4792,7 @@ static void check_msg_timeout(struct ipmi_smi *intf, struct seq_table *ent,
                 * only for messages to the local MC, which don't get
                 * resent.
                 */
-               handlers = intf->handlers;
-               if (handlers) {
+               if (intf->handlers) {
                        if (is_lan_addr(&ent->recv_msg->addr))
                                ipmi_inc_stat(intf,
                                              retransmitted_lan_commands);
@@ -4730,7 +4800,7 @@ static void check_msg_timeout(struct ipmi_smi *intf, struct seq_table *ent,
                                ipmi_inc_stat(intf,
                                              retransmitted_ipmb_commands);
 
-                       smi_send(intf, handlers, smi_msg, 0);
+                       smi_send(intf, intf->handlers, smi_msg, 0);
                } else
                        ipmi_free_smi_msg(smi_msg);
 
@@ -4822,12 +4892,12 @@ static atomic_t stop_operation;
 static void ipmi_timeout(struct timer_list *unused)
 {
        struct ipmi_smi *intf;
-       int nt = 0;
+       int nt = 0, index;
 
        if (atomic_read(&stop_operation))
                return;
 
-       rcu_read_lock();
+       index = srcu_read_lock(&ipmi_interfaces_srcu);
        list_for_each_entry_rcu(intf, &ipmi_interfaces, link) {
                int lnt = 0;
 
@@ -4850,7 +4920,7 @@ static void ipmi_timeout(struct timer_list *unused)
 
                nt += lnt;
        }
-       rcu_read_unlock();
+       srcu_read_unlock(&ipmi_interfaces_srcu, index);
 
        if (nt)
                mod_timer(&ipmi_timer, jiffies + IPMI_TIMEOUT_JIFFIES);