iwlwifi: fix dynamic loading

Add locking to the dynamic loading code to prevent
corrupting the list if multiple device ever init at
the same time (which cannot happen for multiple PCI
devices, but could happen when different busses init
concurrently.)

Also remove a device from the list when it stops so
the list isn't left corrupted, including a fix from
Don to not crash when it was never added.

Reviewed-by: Donald H Fry <donald.h.fry@intel.com>
Tested-by: Donald H Fry <donald.h.fry@intel.com>
Reviewed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Don Fry <donald.h.fry@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.c b/drivers/net/wireless/iwlwifi/iwl-drv.c
index cdfdfae..67c9668 100644
--- a/drivers/net/wireless/iwlwifi/iwl-drv.c
+++ b/drivers/net/wireless/iwlwifi/iwl-drv.c
@@ -131,6 +131,8 @@
 #define DVM_OP_MODE	0
 #define MVM_OP_MODE	1
 
+/* Protects the table contents, i.e. the ops pointer & drv list */
+static struct mutex iwlwifi_opmode_table_mtx;
 static struct iwlwifi_opmode_table {
 	const char *name;			/* name: iwldvm, iwlmvm, etc */
 	const struct iwl_op_mode_ops *ops;	/* pointer to op_mode ops */
@@ -899,6 +901,7 @@
 	release_firmware(ucode_raw);
 	complete(&drv->request_firmware_complete);
 
+	mutex_lock(&iwlwifi_opmode_table_mtx);
 	op = &iwlwifi_opmode_table[DVM_OP_MODE];
 
 	/* add this device to the list of devices using this op_mode */
@@ -910,6 +913,7 @@
 	} else {
 		request_module_nowait("%s", op->name);
 	}
+	mutex_unlock(&iwlwifi_opmode_table_mtx);
 
 	return;
 
@@ -944,6 +948,7 @@
 	drv->cfg = cfg;
 
 	init_completion(&drv->request_firmware_complete);
+	INIT_LIST_HEAD(&drv->list);
 
 	ret = iwl_request_firmware(drv, true);
 
@@ -966,6 +971,16 @@
 
 	iwl_dealloc_ucode(drv);
 
+	mutex_lock(&iwlwifi_opmode_table_mtx);
+	/*
+	 * List is empty (this item wasn't added)
+	 * when firmware loading failed -- in that
+	 * case we can't remove it from any list.
+	 */
+	if (!list_empty(&drv->list))
+		list_del(&drv->list);
+	mutex_unlock(&iwlwifi_opmode_table_mtx);
+
 	kfree(drv);
 }
 
@@ -988,6 +1003,7 @@
 	int i;
 	struct iwl_drv *drv;
 
+	mutex_lock(&iwlwifi_opmode_table_mtx);
 	for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) {
 		if (strcmp(iwlwifi_opmode_table[i].name, name))
 			continue;
@@ -995,8 +1011,10 @@
 		list_for_each_entry(drv, &iwlwifi_opmode_table[i].drv, list)
 			drv->op_mode = ops->start(drv->trans, drv->cfg,
 						  &drv->fw);
+		mutex_unlock(&iwlwifi_opmode_table_mtx);
 		return 0;
 	}
+	mutex_unlock(&iwlwifi_opmode_table_mtx);
 	return -EIO;
 }
 EXPORT_SYMBOL_GPL(iwl_opmode_register);
@@ -1006,6 +1024,7 @@
 	int i;
 	struct iwl_drv *drv;
 
+	mutex_lock(&iwlwifi_opmode_table_mtx);
 	for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) {
 		if (strcmp(iwlwifi_opmode_table[i].name, name))
 			continue;
@@ -1018,8 +1037,10 @@
 				drv->op_mode = NULL;
 			}
 		}
+		mutex_unlock(&iwlwifi_opmode_table_mtx);
 		return;
 	}
+	mutex_unlock(&iwlwifi_opmode_table_mtx);
 }
 EXPORT_SYMBOL_GPL(iwl_opmode_deregister);
 
@@ -1027,6 +1048,8 @@
 {
 	int i;
 
+	mutex_init(&iwlwifi_opmode_table_mtx);
+
 	for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++)
 		INIT_LIST_HEAD(&iwlwifi_opmode_table[i].drv);