linux/drivers/net/wireless/intel/iwlwifi/iwl-trans.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
   2/*
   3 * Copyright (C) 2015 Intel Mobile Communications GmbH
   4 * Copyright (C) 2016-2017 Intel Deutschland GmbH
   5 * Copyright (C) 2019-2021 Intel Corporation
   6 */
   7#include <linux/kernel.h>
   8#include <linux/bsearch.h>
   9
  10#include "fw/api/tx.h"
  11#include "iwl-trans.h"
  12#include "iwl-drv.h"
  13#include "iwl-fh.h"
  14#include "queue/tx.h"
  15#include <linux/dmapool.h>
  16#include "fw/api/commands.h"
  17
  18struct iwl_trans *iwl_trans_alloc(unsigned int priv_size,
  19                                  struct device *dev,
  20                                  const struct iwl_trans_ops *ops,
  21                                  const struct iwl_cfg_trans_params *cfg_trans)
  22{
  23        struct iwl_trans *trans;
  24#ifdef CONFIG_LOCKDEP
  25        static struct lock_class_key __key;
  26#endif
  27
  28        trans = devm_kzalloc(dev, sizeof(*trans) + priv_size, GFP_KERNEL);
  29        if (!trans)
  30                return NULL;
  31
  32        trans->trans_cfg = cfg_trans;
  33
  34#ifdef CONFIG_LOCKDEP
  35        lockdep_init_map(&trans->sync_cmd_lockdep_map, "sync_cmd_lockdep_map",
  36                         &__key, 0);
  37#endif
  38
  39        trans->dev = dev;
  40        trans->ops = ops;
  41        trans->num_rx_queues = 1;
  42
  43        WARN_ON(!ops->wait_txq_empty && !ops->wait_tx_queues_empty);
  44
  45        if (trans->trans_cfg->use_tfh) {
  46                trans->txqs.tfd.addr_size = 64;
  47                trans->txqs.tfd.max_tbs = IWL_TFH_NUM_TBS;
  48                trans->txqs.tfd.size = sizeof(struct iwl_tfh_tfd);
  49        } else {
  50                trans->txqs.tfd.addr_size = 36;
  51                trans->txqs.tfd.max_tbs = IWL_NUM_OF_TBS;
  52                trans->txqs.tfd.size = sizeof(struct iwl_tfd);
  53        }
  54        trans->max_skb_frags = IWL_TRANS_MAX_FRAGS(trans);
  55
  56        return trans;
  57}
  58
  59int iwl_trans_init(struct iwl_trans *trans)
  60{
  61        int txcmd_size, txcmd_align;
  62
  63        if (!trans->trans_cfg->gen2) {
  64                txcmd_size = sizeof(struct iwl_tx_cmd);
  65                txcmd_align = sizeof(void *);
  66        } else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) {
  67                txcmd_size = sizeof(struct iwl_tx_cmd_gen2);
  68                txcmd_align = 64;
  69        } else {
  70                txcmd_size = sizeof(struct iwl_tx_cmd_gen3);
  71                txcmd_align = 128;
  72        }
  73
  74        txcmd_size += sizeof(struct iwl_cmd_header);
  75        txcmd_size += 36; /* biggest possible 802.11 header */
  76
  77        /* Ensure device TX cmd cannot reach/cross a page boundary in gen2 */
  78        if (WARN_ON(trans->trans_cfg->gen2 && txcmd_size >= txcmd_align))
  79                return -EINVAL;
  80
  81        if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210)
  82                trans->txqs.bc_tbl_size = sizeof(struct iwl_gen3_bc_tbl);
  83        else
  84                trans->txqs.bc_tbl_size = sizeof(struct iwlagn_scd_bc_tbl);
  85        /*
  86         * For gen2 devices, we use a single allocation for each byte-count
  87         * table, but they're pretty small (1k) so use a DMA pool that we
  88         * allocate here.
  89         */
  90        if (trans->trans_cfg->gen2) {
  91                trans->txqs.bc_pool = dmam_pool_create("iwlwifi:bc", trans->dev,
  92                                                       trans->txqs.bc_tbl_size,
  93                                                       256, 0);
  94                if (!trans->txqs.bc_pool)
  95                        return -ENOMEM;
  96        }
  97
  98        /* Some things must not change even if the config does */
  99        WARN_ON(trans->txqs.tfd.addr_size !=
 100                (trans->trans_cfg->use_tfh ? 64 : 36));
 101
 102        snprintf(trans->dev_cmd_pool_name, sizeof(trans->dev_cmd_pool_name),
 103                 "iwl_cmd_pool:%s", dev_name(trans->dev));
 104        trans->dev_cmd_pool =
 105                kmem_cache_create(trans->dev_cmd_pool_name,
 106                                  txcmd_size, txcmd_align,
 107                                  SLAB_HWCACHE_ALIGN, NULL);
 108        if (!trans->dev_cmd_pool)
 109                return -ENOMEM;
 110
 111        trans->txqs.tso_hdr_page = alloc_percpu(struct iwl_tso_hdr_page);
 112        if (!trans->txqs.tso_hdr_page) {
 113                kmem_cache_destroy(trans->dev_cmd_pool);
 114                return -ENOMEM;
 115        }
 116
 117        /* Initialize the wait queue for commands */
 118        init_waitqueue_head(&trans->wait_command_queue);
 119
 120        return 0;
 121}
 122
 123void iwl_trans_free(struct iwl_trans *trans)
 124{
 125        int i;
 126
 127        if (trans->txqs.tso_hdr_page) {
 128                for_each_possible_cpu(i) {
 129                        struct iwl_tso_hdr_page *p =
 130                                per_cpu_ptr(trans->txqs.tso_hdr_page, i);
 131
 132                        if (p && p->page)
 133                                __free_page(p->page);
 134                }
 135
 136                free_percpu(trans->txqs.tso_hdr_page);
 137        }
 138
 139        kmem_cache_destroy(trans->dev_cmd_pool);
 140}
 141
 142int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
 143{
 144        int ret;
 145
 146        if (unlikely(!(cmd->flags & CMD_SEND_IN_RFKILL) &&
 147                     test_bit(STATUS_RFKILL_OPMODE, &trans->status)))
 148                return -ERFKILL;
 149
 150        /*
 151         * We can't test IWL_MVM_STATUS_IN_D3 in mvm->status because this
 152         * bit is set early in the D3 flow, before we send all the commands
 153         * that configure the firmware for D3 operation (power, patterns, ...)
 154         * and we don't want to flag all those with CMD_SEND_IN_D3.
 155         * So use the system_pm_mode instead. The only command sent after
 156         * we set system_pm_mode is D3_CONFIG_CMD, which we now flag with
 157         * CMD_SEND_IN_D3.
 158         */
 159        if (unlikely(trans->system_pm_mode == IWL_PLAT_PM_MODE_D3 &&
 160                     !(cmd->flags & CMD_SEND_IN_D3)))
 161                return -EHOSTDOWN;
 162
 163        if (unlikely(test_bit(STATUS_FW_ERROR, &trans->status)))
 164                return -EIO;
 165
 166        if (unlikely(trans->state != IWL_TRANS_FW_ALIVE)) {
 167                IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state);
 168                return -EIO;
 169        }
 170
 171        if (WARN_ON((cmd->flags & CMD_WANT_ASYNC_CALLBACK) &&
 172                    !(cmd->flags & CMD_ASYNC)))
 173                return -EINVAL;
 174
 175        if (!(cmd->flags & CMD_ASYNC))
 176                lock_map_acquire_read(&trans->sync_cmd_lockdep_map);
 177
 178        if (trans->wide_cmd_header && !iwl_cmd_groupid(cmd->id)) {
 179                if (cmd->id != REPLY_ERROR)
 180                        cmd->id = DEF_ID(cmd->id);
 181        }
 182
 183        ret = iwl_trans_txq_send_hcmd(trans, cmd);
 184
 185        if (!(cmd->flags & CMD_ASYNC))
 186                lock_map_release(&trans->sync_cmd_lockdep_map);
 187
 188        if (WARN_ON((cmd->flags & CMD_WANT_SKB) && !ret && !cmd->resp_pkt))
 189                return -EIO;
 190
 191        return ret;
 192}
 193IWL_EXPORT_SYMBOL(iwl_trans_send_cmd);
 194
 195/* Comparator for struct iwl_hcmd_names.
 196 * Used in the binary search over a list of host commands.
 197 *
 198 * @key: command_id that we're looking for.
 199 * @elt: struct iwl_hcmd_names candidate for match.
 200 *
 201 * @return 0 iff equal.
 202 */
 203static int iwl_hcmd_names_cmp(const void *key, const void *elt)
 204{
 205        const struct iwl_hcmd_names *name = elt;
 206        u8 cmd1 = *(u8 *)key;
 207        u8 cmd2 = name->cmd_id;
 208
 209        return (cmd1 - cmd2);
 210}
 211
 212const char *iwl_get_cmd_string(struct iwl_trans *trans, u32 id)
 213{
 214        u8 grp, cmd;
 215        struct iwl_hcmd_names *ret;
 216        const struct iwl_hcmd_arr *arr;
 217        size_t size = sizeof(struct iwl_hcmd_names);
 218
 219        grp = iwl_cmd_groupid(id);
 220        cmd = iwl_cmd_opcode(id);
 221
 222        if (!trans->command_groups || grp >= trans->command_groups_size ||
 223            !trans->command_groups[grp].arr)
 224                return "UNKNOWN";
 225
 226        arr = &trans->command_groups[grp];
 227        ret = bsearch(&cmd, arr->arr, arr->size, size, iwl_hcmd_names_cmp);
 228        if (!ret)
 229                return "UNKNOWN";
 230        return ret->cmd_name;
 231}
 232IWL_EXPORT_SYMBOL(iwl_get_cmd_string);
 233
 234int iwl_cmd_groups_verify_sorted(const struct iwl_trans_config *trans)
 235{
 236        int i, j;
 237        const struct iwl_hcmd_arr *arr;
 238
 239        for (i = 0; i < trans->command_groups_size; i++) {
 240                arr = &trans->command_groups[i];
 241                if (!arr->arr)
 242                        continue;
 243                for (j = 0; j < arr->size - 1; j++)
 244                        if (arr->arr[j].cmd_id > arr->arr[j + 1].cmd_id)
 245                                return -1;
 246        }
 247        return 0;
 248}
 249IWL_EXPORT_SYMBOL(iwl_cmd_groups_verify_sorted);
 250