linux/drivers/net/wireless/intel/iwlwifi/fw/paging.c
<<
>>
Prefs
   1/******************************************************************************
   2 *
   3 * This file is provided under a dual BSD/GPLv2 license.  When using or
   4 * redistributing this file, you may do so under either license.
   5 *
   6 * GPL LICENSE SUMMARY
   7 *
   8 * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
   9 * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
  10 * Copyright(c) 2016 - 2017 Intel Deutschland GmbH
  11 *
  12 * This program is free software; you can redistribute it and/or modify
  13 * it under the terms of version 2 of the GNU General Public License as
  14 * published by the Free Software Foundation.
  15 *
  16 * This program is distributed in the hope that it will be useful, but
  17 * WITHOUT ANY WARRANTY; without even the implied warranty of
  18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  19 * General Public License for more details.
  20 *
  21 * The full GNU General Public License is included in this distribution
  22 * in the file called COPYING.
  23 *
  24 * Contact Information:
  25 *  Intel Linux Wireless <linuxwifi@intel.com>
  26 * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
  27 *
  28 * BSD LICENSE
  29 *
  30 * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
  31 * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
  32 * Copyright(c) 2016 - 2017 Intel Deutschland GmbH
  33 * All rights reserved.
  34 *
  35 * Redistribution and use in source and binary forms, with or without
  36 * modification, are permitted provided that the following conditions
  37 * are met:
  38 *
  39 *  * Redistributions of source code must retain the above copyright
  40 *    notice, this list of conditions and the following disclaimer.
  41 *  * Redistributions in binary form must reproduce the above copyright
  42 *    notice, this list of conditions and the following disclaimer in
  43 *    the documentation and/or other materials provided with the
  44 *    distribution.
  45 *  * Neither the name Intel Corporation nor the names of its
  46 *    contributors may be used to endorse or promote products derived
  47 *    from this software without specific prior written permission.
  48 *
  49 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  50 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  51 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  52 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  53 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  54 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  55 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  56 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  57 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  58 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  59 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  60 *
  61 *****************************************************************************/
  62#include "iwl-drv.h"
  63#include "runtime.h"
  64#include "fw/api/commands.h"
  65
  66void iwl_free_fw_paging(struct iwl_fw_runtime *fwrt)
  67{
  68        int i;
  69
  70        if (!fwrt->fw_paging_db[0].fw_paging_block)
  71                return;
  72
  73        for (i = 0; i < NUM_OF_FW_PAGING_BLOCKS; i++) {
  74                struct iwl_fw_paging *paging = &fwrt->fw_paging_db[i];
  75
  76                if (!paging->fw_paging_block) {
  77                        IWL_DEBUG_FW(fwrt,
  78                                     "Paging: block %d already freed, continue to next page\n",
  79                                     i);
  80
  81                        continue;
  82                }
  83                dma_unmap_page(fwrt->trans->dev, paging->fw_paging_phys,
  84                               paging->fw_paging_size, DMA_BIDIRECTIONAL);
  85
  86                __free_pages(paging->fw_paging_block,
  87                             get_order(paging->fw_paging_size));
  88                paging->fw_paging_block = NULL;
  89        }
  90        kfree(fwrt->trans->paging_download_buf);
  91        fwrt->trans->paging_download_buf = NULL;
  92        fwrt->trans->paging_db = NULL;
  93
  94        memset(fwrt->fw_paging_db, 0, sizeof(fwrt->fw_paging_db));
  95}
  96IWL_EXPORT_SYMBOL(iwl_free_fw_paging);
  97
  98static int iwl_alloc_fw_paging_mem(struct iwl_fw_runtime *fwrt,
  99                                   const struct fw_img *image)
 100{
 101        struct page *block;
 102        dma_addr_t phys = 0;
 103        int blk_idx, order, num_of_pages, size, dma_enabled;
 104
 105        if (fwrt->fw_paging_db[0].fw_paging_block)
 106                return 0;
 107
 108        dma_enabled = is_device_dma_capable(fwrt->trans->dev);
 109
 110        /* ensure BLOCK_2_EXP_SIZE is power of 2 of PAGING_BLOCK_SIZE */
 111        BUILD_BUG_ON(BIT(BLOCK_2_EXP_SIZE) != PAGING_BLOCK_SIZE);
 112
 113        num_of_pages = image->paging_mem_size / FW_PAGING_SIZE;
 114        fwrt->num_of_paging_blk =
 115                DIV_ROUND_UP(num_of_pages, NUM_OF_PAGE_PER_GROUP);
 116        fwrt->num_of_pages_in_last_blk =
 117                num_of_pages -
 118                NUM_OF_PAGE_PER_GROUP * (fwrt->num_of_paging_blk - 1);
 119
 120        IWL_DEBUG_FW(fwrt,
 121                     "Paging: allocating mem for %d paging blocks, each block holds 8 pages, last block holds %d pages\n",
 122                     fwrt->num_of_paging_blk,
 123                     fwrt->num_of_pages_in_last_blk);
 124
 125        /*
 126         * Allocate CSS and paging blocks in dram.
 127         */
 128        for (blk_idx = 0; blk_idx < fwrt->num_of_paging_blk + 1; blk_idx++) {
 129                /* For CSS allocate 4KB, for others PAGING_BLOCK_SIZE (32K) */
 130                size = blk_idx ? PAGING_BLOCK_SIZE : FW_PAGING_SIZE;
 131                order = get_order(size);
 132                block = alloc_pages(GFP_KERNEL, order);
 133                if (!block) {
 134                        /* free all the previous pages since we failed */
 135                        iwl_free_fw_paging(fwrt);
 136                        return -ENOMEM;
 137                }
 138
 139                fwrt->fw_paging_db[blk_idx].fw_paging_block = block;
 140                fwrt->fw_paging_db[blk_idx].fw_paging_size = size;
 141
 142                if (dma_enabled) {
 143                        phys = dma_map_page(fwrt->trans->dev, block, 0,
 144                                            PAGE_SIZE << order,
 145                                            DMA_BIDIRECTIONAL);
 146                        if (dma_mapping_error(fwrt->trans->dev, phys)) {
 147                                /*
 148                                 * free the previous pages and the current one
 149                                 * since we failed to map_page.
 150                                 */
 151                                iwl_free_fw_paging(fwrt);
 152                                return -ENOMEM;
 153                        }
 154                        fwrt->fw_paging_db[blk_idx].fw_paging_phys = phys;
 155                } else {
 156                        fwrt->fw_paging_db[blk_idx].fw_paging_phys =
 157                                PAGING_ADDR_SIG |
 158                                blk_idx << BLOCK_2_EXP_SIZE;
 159                }
 160
 161                if (!blk_idx)
 162                        IWL_DEBUG_FW(fwrt,
 163                                     "Paging: allocated 4K(CSS) bytes (order %d) for firmware paging.\n",
 164                                     order);
 165                else
 166                        IWL_DEBUG_FW(fwrt,
 167                                     "Paging: allocated 32K bytes (order %d) for firmware paging.\n",
 168                                     order);
 169        }
 170
 171        return 0;
 172}
 173
 174static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt,
 175                               const struct fw_img *image)
 176{
 177        int sec_idx, idx;
 178        u32 offset = 0;
 179
 180        /*
 181         * find where is the paging image start point:
 182         * if CPU2 exist and it's in paging format, then the image looks like:
 183         * CPU1 sections (2 or more)
 184         * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between CPU1 to CPU2
 185         * CPU2 sections (not paged)
 186         * PAGING_SEPARATOR_SECTION delimiter - separate between CPU2
 187         * non paged to CPU2 paging sec
 188         * CPU2 paging CSS
 189         * CPU2 paging image (including instruction and data)
 190         */
 191        for (sec_idx = 0; sec_idx < image->num_sec; sec_idx++) {
 192                if (image->sec[sec_idx].offset == PAGING_SEPARATOR_SECTION) {
 193                        sec_idx++;
 194                        break;
 195                }
 196        }
 197
 198        /*
 199         * If paging is enabled there should be at least 2 more sections left
 200         * (one for CSS and one for Paging data)
 201         */
 202        if (sec_idx >= image->num_sec - 1) {
 203                IWL_ERR(fwrt, "Paging: Missing CSS and/or paging sections\n");
 204                iwl_free_fw_paging(fwrt);
 205                return -EINVAL;
 206        }
 207
 208        /* copy the CSS block to the dram */
 209        IWL_DEBUG_FW(fwrt, "Paging: load paging CSS to FW, sec = %d\n",
 210                     sec_idx);
 211
 212        memcpy(page_address(fwrt->fw_paging_db[0].fw_paging_block),
 213               image->sec[sec_idx].data,
 214               fwrt->fw_paging_db[0].fw_paging_size);
 215        dma_sync_single_for_device(fwrt->trans->dev,
 216                                   fwrt->fw_paging_db[0].fw_paging_phys,
 217                                   fwrt->fw_paging_db[0].fw_paging_size,
 218                                   DMA_BIDIRECTIONAL);
 219
 220        IWL_DEBUG_FW(fwrt,
 221                     "Paging: copied %d CSS bytes to first block\n",
 222                     fwrt->fw_paging_db[0].fw_paging_size);
 223
 224        sec_idx++;
 225
 226        /*
 227         * copy the paging blocks to the dram
 228         * loop index start from 1 since that CSS block already copied to dram
 229         * and CSS index is 0.
 230         * loop stop at num_of_paging_blk since that last block is not full.
 231         */
 232        for (idx = 1; idx < fwrt->num_of_paging_blk; idx++) {
 233                struct iwl_fw_paging *block = &fwrt->fw_paging_db[idx];
 234
 235                memcpy(page_address(block->fw_paging_block),
 236                       image->sec[sec_idx].data + offset,
 237                       block->fw_paging_size);
 238                dma_sync_single_for_device(fwrt->trans->dev,
 239                                           block->fw_paging_phys,
 240                                           block->fw_paging_size,
 241                                           DMA_BIDIRECTIONAL);
 242
 243                IWL_DEBUG_FW(fwrt,
 244                             "Paging: copied %d paging bytes to block %d\n",
 245                             fwrt->fw_paging_db[idx].fw_paging_size,
 246                             idx);
 247
 248                offset += fwrt->fw_paging_db[idx].fw_paging_size;
 249        }
 250
 251        /* copy the last paging block */
 252        if (fwrt->num_of_pages_in_last_blk > 0) {
 253                struct iwl_fw_paging *block = &fwrt->fw_paging_db[idx];
 254
 255                memcpy(page_address(block->fw_paging_block),
 256                       image->sec[sec_idx].data + offset,
 257                       FW_PAGING_SIZE * fwrt->num_of_pages_in_last_blk);
 258                dma_sync_single_for_device(fwrt->trans->dev,
 259                                           block->fw_paging_phys,
 260                                           block->fw_paging_size,
 261                                           DMA_BIDIRECTIONAL);
 262
 263                IWL_DEBUG_FW(fwrt,
 264                             "Paging: copied %d pages in the last block %d\n",
 265                             fwrt->num_of_pages_in_last_blk, idx);
 266        }
 267
 268        return 0;
 269}
 270
 271static int iwl_save_fw_paging(struct iwl_fw_runtime *fwrt,
 272                              const struct fw_img *fw)
 273{
 274        int ret;
 275
 276        ret = iwl_alloc_fw_paging_mem(fwrt, fw);
 277        if (ret)
 278                return ret;
 279
 280        return iwl_fill_paging_mem(fwrt, fw);
 281}
 282
 283/* send paging cmd to FW in case CPU2 has paging image */
 284static int iwl_send_paging_cmd(struct iwl_fw_runtime *fwrt,
 285                               const struct fw_img *fw)
 286{
 287        struct iwl_fw_paging_cmd paging_cmd = {
 288                .flags = cpu_to_le32(PAGING_CMD_IS_SECURED |
 289                                     PAGING_CMD_IS_ENABLED |
 290                                     (fwrt->num_of_pages_in_last_blk <<
 291                                      PAGING_CMD_NUM_OF_PAGES_IN_LAST_GRP_POS)),
 292                .block_size = cpu_to_le32(BLOCK_2_EXP_SIZE),
 293                .block_num = cpu_to_le32(fwrt->num_of_paging_blk),
 294        };
 295        struct iwl_host_cmd hcmd = {
 296                .id = iwl_cmd_id(FW_PAGING_BLOCK_CMD, IWL_ALWAYS_LONG_GROUP, 0),
 297                .len = { sizeof(paging_cmd), },
 298                .data = { &paging_cmd, },
 299        };
 300        int blk_idx;
 301
 302        /* loop for for all paging blocks + CSS block */
 303        for (blk_idx = 0; blk_idx < fwrt->num_of_paging_blk + 1; blk_idx++) {
 304                dma_addr_t addr = fwrt->fw_paging_db[blk_idx].fw_paging_phys;
 305                __le32 phy_addr;
 306
 307                addr = addr >> PAGE_2_EXP_SIZE;
 308                phy_addr = cpu_to_le32(addr);
 309                paging_cmd.device_phy_addr[blk_idx] = phy_addr;
 310        }
 311
 312        return iwl_trans_send_cmd(fwrt->trans, &hcmd);
 313}
 314
 315/*
 316 * Send paging item cmd to FW in case CPU2 has paging image
 317 */
 318static int iwl_trans_get_paging_item(struct iwl_fw_runtime *fwrt)
 319{
 320        int ret;
 321        struct iwl_fw_get_item_cmd fw_get_item_cmd = {
 322                .item_id = cpu_to_le32(IWL_FW_ITEM_ID_PAGING),
 323        };
 324        struct iwl_fw_get_item_resp *item_resp;
 325        struct iwl_host_cmd cmd = {
 326                .id = iwl_cmd_id(FW_GET_ITEM_CMD, IWL_ALWAYS_LONG_GROUP, 0),
 327                .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL,
 328                .data = { &fw_get_item_cmd, },
 329                .len = { sizeof(fw_get_item_cmd), },
 330        };
 331
 332        ret = iwl_trans_send_cmd(fwrt->trans, &cmd);
 333        if (ret) {
 334                IWL_ERR(fwrt,
 335                        "Paging: Failed to send FW_GET_ITEM_CMD cmd (err = %d)\n",
 336                        ret);
 337                return ret;
 338        }
 339
 340        item_resp = (void *)((struct iwl_rx_packet *)cmd.resp_pkt)->data;
 341        if (item_resp->item_id != cpu_to_le32(IWL_FW_ITEM_ID_PAGING)) {
 342                IWL_ERR(fwrt,
 343                        "Paging: got wrong item in FW_GET_ITEM_CMD resp (item_id = %u)\n",
 344                        le32_to_cpu(item_resp->item_id));
 345                ret = -EIO;
 346                goto exit;
 347        }
 348
 349        /* Add an extra page for headers */
 350        fwrt->trans->paging_download_buf = kzalloc(PAGING_BLOCK_SIZE +
 351                                                  FW_PAGING_SIZE,
 352                                                  GFP_KERNEL);
 353        if (!fwrt->trans->paging_download_buf) {
 354                ret = -ENOMEM;
 355                goto exit;
 356        }
 357        fwrt->trans->paging_req_addr = le32_to_cpu(item_resp->item_val);
 358        fwrt->trans->paging_db = fwrt->fw_paging_db;
 359        IWL_DEBUG_FW(fwrt,
 360                     "Paging: got paging request address (paging_req_addr 0x%08x)\n",
 361                     fwrt->trans->paging_req_addr);
 362
 363exit:
 364        iwl_free_resp(&cmd);
 365
 366        return ret;
 367}
 368
 369int iwl_init_paging(struct iwl_fw_runtime *fwrt, enum iwl_ucode_type type)
 370{
 371        const struct fw_img *fw = &fwrt->fw->img[type];
 372        int ret;
 373
 374        if (fwrt->trans->cfg->gen2)
 375                return 0;
 376
 377        /*
 378         * Configure and operate fw paging mechanism.
 379         * The driver configures the paging flow only once.
 380         * The CPU2 paging image is included in the IWL_UCODE_INIT image.
 381         */
 382        if (!fw->paging_mem_size)
 383                return 0;
 384
 385        /*
 386         * When dma is not enabled, the driver needs to copy / write
 387         * the downloaded / uploaded page to / from the smem.
 388         * This gets the location of the place were the pages are
 389         * stored.
 390         */
 391        if (!is_device_dma_capable(fwrt->trans->dev)) {
 392                ret = iwl_trans_get_paging_item(fwrt);
 393                if (ret) {
 394                        IWL_ERR(fwrt, "failed to get FW paging item\n");
 395                        return ret;
 396                }
 397        }
 398
 399        ret = iwl_save_fw_paging(fwrt, fw);
 400        if (ret) {
 401                IWL_ERR(fwrt, "failed to save the FW paging image\n");
 402                return ret;
 403        }
 404
 405        ret = iwl_send_paging_cmd(fwrt, fw);
 406        if (ret) {
 407                IWL_ERR(fwrt, "failed to send the paging cmd\n");
 408                iwl_free_fw_paging(fwrt);
 409                return ret;
 410        }
 411
 412        return 0;
 413}
 414IWL_EXPORT_SYMBOL(iwl_init_paging);
 415