From d12455fdbfe9430affd88bfbfee51777356667a0 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 11 Apr 2021 13:25:41 +0300 Subject: [PATCH] iwlwifi: trans/pcie: defer transport initialisation In a few PCIe devices we may have to swap out the configuration after we allocate/initialise some parts of the device because we only know the correct one after reading some registers. This causes some things such as the byte-count table allocations to be incorrect, since the configuration is swapped for one with a bigger queue size. Fix this by initialising most of the transport much later, only after the configuration has finally been determined. Signed-off-by: Johannes Berg Signed-off-by: Luca Coelho Link: https://lore.kernel.org/r/iwlwifi.20210411132130.8f5db97db1e4.Ic622da559b586a04ca536a0ec49ed5ecf03a9354@changeid Signed-off-by: Luca Coelho --- .../net/wireless/intel/iwlwifi/iwl-trans.c | 107 ++++++++++-------- .../net/wireless/intel/iwlwifi/iwl-trans.h | 1 + drivers/net/wireless/intel/iwlwifi/pcie/drv.c | 4 + 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index 60e0db4a5e20..9236f9106826 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -2,7 +2,7 @@ /* * Copyright (C) 2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH - * Copyright (C) 2019-2020 Intel Corporation + * Copyright (C) 2019-2021 Intel Corporation */ #include #include @@ -21,7 +21,6 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, const struct iwl_cfg_trans_params *cfg_trans) { struct iwl_trans *trans; - int txcmd_size, txcmd_align; #ifdef CONFIG_LOCKDEP static struct lock_class_key __key; #endif @@ -31,23 +30,6 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, return NULL; trans->trans_cfg = cfg_trans; - if (!cfg_trans->gen2) { - txcmd_size = sizeof(struct iwl_tx_cmd); - txcmd_align = sizeof(void *); - } else if (cfg_trans->device_family < IWL_DEVICE_FAMILY_AX210) { - txcmd_size = sizeof(struct iwl_tx_cmd_gen2); - txcmd_align = 64; - } else { - txcmd_size = sizeof(struct iwl_tx_cmd_gen3); - txcmd_align = 128; - } - - txcmd_size += sizeof(struct iwl_cmd_header); - txcmd_size += 36; /* biggest possible 802.11 header */ - - /* Ensure device TX cmd cannot reach/cross a page boundary in gen2 */ - if (WARN_ON(cfg_trans->gen2 && txcmd_size >= txcmd_align)) - return ERR_PTR(-EINVAL); #ifdef CONFIG_LOCKDEP lockdep_init_map(&trans->sync_cmd_lockdep_map, "sync_cmd_lockdep_map", @@ -58,22 +40,7 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, trans->ops = ops; trans->num_rx_queues = 1; - if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) - trans->txqs.bc_tbl_size = sizeof(struct iwl_gen3_bc_tbl); - else - trans->txqs.bc_tbl_size = sizeof(struct iwlagn_scd_bc_tbl); - /* - * For gen2 devices, we use a single allocation for each byte-count - * table, but they're pretty small (1k) so use a DMA pool that we - * allocate here. - */ - if (trans->trans_cfg->gen2) { - trans->txqs.bc_pool = dmam_pool_create("iwlwifi:bc", dev, - trans->txqs.bc_tbl_size, - 256, 0); - if (!trans->txqs.bc_pool) - return NULL; - } + WARN_ON(!ops->wait_txq_empty && !ops->wait_tx_queues_empty); if (trans->trans_cfg->use_tfh) { trans->txqs.tfd.addr_size = 64; @@ -86,6 +53,52 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, } trans->max_skb_frags = IWL_TRANS_MAX_FRAGS(trans); + return trans; +} + +int iwl_trans_init(struct iwl_trans *trans) +{ + int txcmd_size, txcmd_align; + + if (!trans->trans_cfg->gen2) { + txcmd_size = sizeof(struct iwl_tx_cmd); + txcmd_align = sizeof(void *); + } else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { + txcmd_size = sizeof(struct iwl_tx_cmd_gen2); + txcmd_align = 64; + } else { + txcmd_size = sizeof(struct iwl_tx_cmd_gen3); + txcmd_align = 128; + } + + txcmd_size += sizeof(struct iwl_cmd_header); + txcmd_size += 36; /* biggest possible 802.11 header */ + + /* Ensure device TX cmd cannot reach/cross a page boundary in gen2 */ + if (WARN_ON(trans->trans_cfg->gen2 && txcmd_size >= txcmd_align)) + return -EINVAL; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + trans->txqs.bc_tbl_size = sizeof(struct iwl_gen3_bc_tbl); + else + trans->txqs.bc_tbl_size = sizeof(struct iwlagn_scd_bc_tbl); + /* + * For gen2 devices, we use a single allocation for each byte-count + * table, but they're pretty small (1k) so use a DMA pool that we + * allocate here. + */ + if (trans->trans_cfg->gen2) { + trans->txqs.bc_pool = dmam_pool_create("iwlwifi:bc", trans->dev, + trans->txqs.bc_tbl_size, + 256, 0); + if (!trans->txqs.bc_pool) + return -ENOMEM; + } + + /* Some things must not change even if the config does */ + WARN_ON(trans->txqs.tfd.addr_size != + (trans->trans_cfg->use_tfh ? 64 : 36)); + snprintf(trans->dev_cmd_pool_name, sizeof(trans->dev_cmd_pool_name), "iwl_cmd_pool:%s", dev_name(trans->dev)); trans->dev_cmd_pool = @@ -93,36 +106,36 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, txcmd_size, txcmd_align, SLAB_HWCACHE_ALIGN, NULL); if (!trans->dev_cmd_pool) - return NULL; - - WARN_ON(!ops->wait_txq_empty && !ops->wait_tx_queues_empty); + return -ENOMEM; trans->txqs.tso_hdr_page = alloc_percpu(struct iwl_tso_hdr_page); if (!trans->txqs.tso_hdr_page) { kmem_cache_destroy(trans->dev_cmd_pool); - return NULL; + return -ENOMEM; } /* Initialize the wait queue for commands */ init_waitqueue_head(&trans->wait_command_queue); - return trans; + return 0; } void iwl_trans_free(struct iwl_trans *trans) { int i; - for_each_possible_cpu(i) { - struct iwl_tso_hdr_page *p = - per_cpu_ptr(trans->txqs.tso_hdr_page, i); + if (trans->txqs.tso_hdr_page) { + for_each_possible_cpu(i) { + struct iwl_tso_hdr_page *p = + per_cpu_ptr(trans->txqs.tso_hdr_page, i); - if (p->page) - __free_page(p->page); + if (p && p->page) + __free_page(p->page); + } + + free_percpu(trans->txqs.tso_hdr_page); } - free_percpu(trans->txqs.tso_hdr_page); - kmem_cache_destroy(trans->dev_cmd_pool); } diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index 6abb7385dae1..bf569f856ad8 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -1439,6 +1439,7 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, struct device *dev, const struct iwl_trans_ops *ops, const struct iwl_cfg_trans_params *cfg_trans); +int iwl_trans_init(struct iwl_trans *trans); void iwl_trans_free(struct iwl_trans *trans); /***************************************************** diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index 729e5503f17a..e33e559f847a 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -1276,6 +1276,10 @@ static int iwl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) trans_pcie->num_rx_bufs = RX_QUEUE_SIZE; } + ret = iwl_trans_init(iwl_trans); + if (ret) + goto out_free_trans; + pci_set_drvdata(pdev, iwl_trans); iwl_trans->drv = iwl_drv_start(iwl_trans);