From ff1ffb850b7ac6d0f1b085208a6630f7584e1015 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 6 Jun 2012 09:42:57 +0200 Subject: [PATCH] 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 Tested-by: Donald H Fry Reviewed-by: Emmanuel Grumbach Signed-off-by: Don Fry Signed-off-by: Johannes Berg --- drivers/net/wireless/iwlwifi/iwl-drv.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.c b/drivers/net/wireless/iwlwifi/iwl-drv.c index cdfdfaec395e..67c9668d2e37 100644 --- a/drivers/net/wireless/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/iwlwifi/iwl-drv.c @@ -131,6 +131,8 @@ struct iwl_drv { #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 @@ static void iwl_ucode_callback(const struct firmware *ucode_raw, void *context) 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 @@ static void iwl_ucode_callback(const struct firmware *ucode_raw, void *context) } else { request_module_nowait("%s", op->name); } + mutex_unlock(&iwlwifi_opmode_table_mtx); return; @@ -944,6 +948,7 @@ struct iwl_drv *iwl_drv_start(struct iwl_trans *trans, drv->cfg = cfg; init_completion(&drv->request_firmware_complete); + INIT_LIST_HEAD(&drv->list); ret = iwl_request_firmware(drv, true); @@ -966,6 +971,16 @@ void iwl_drv_stop(struct iwl_drv *drv) 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 iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops) 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 @@ int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops) 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 @@ void iwl_opmode_deregister(const char *name) 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 @@ void iwl_opmode_deregister(const char *name) 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 @@ static int __init iwl_drv_init(void) { 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); -- 2.20.1