linux/drivers/misc/iwmc3200top/fw-download.c
<<
>>
Prefs
   1/*
   2 * iwmc3200top - Intel Wireless MultiCom 3200 Top Driver
   3 * drivers/misc/iwmc3200top/fw-download.c
   4 *
   5 * Copyright (C) 2009 Intel Corporation. All rights reserved.
   6 *
   7 * This program is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU General Public License version
   9 * 2 as published by the Free Software Foundation.
  10 *
  11 * This program is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 * GNU General Public License for more details.
  15 *
  16 * You should have received a copy of the GNU General Public License
  17 * along with this program; if not, write to the Free Software
  18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19 * 02110-1301, USA.
  20 *
  21 *
  22 * Author Name: Maxim Grabarnik <maxim.grabarnink@intel.com>
  23 *  -
  24 *
  25 */
  26
  27#include <linux/firmware.h>
  28#include <linux/mmc/sdio_func.h>
  29#include <linux/slab.h>
  30#include <asm/unaligned.h>
  31
  32#include "iwmc3200top.h"
  33#include "log.h"
  34#include "fw-msg.h"
  35
  36#define CHECKSUM_BYTES_NUM sizeof(u32)
  37
  38/**
  39  init parser struct with file
  40 */
  41static int iwmct_fw_parser_init(struct iwmct_priv *priv, const u8 *file,
  42                              size_t file_size, size_t block_size)
  43{
  44        struct iwmct_parser *parser = &priv->parser;
  45        struct iwmct_fw_hdr *fw_hdr = &parser->versions;
  46
  47        LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
  48
  49        LOG_INFO(priv, FW_DOWNLOAD, "file_size=%zd\n", file_size);
  50
  51        parser->file = file;
  52        parser->file_size = file_size;
  53        parser->cur_pos = 0;
  54        parser->entry_point = 0;
  55        parser->buf = kzalloc(block_size, GFP_KERNEL);
  56        if (!parser->buf) {
  57                LOG_ERROR(priv, FW_DOWNLOAD, "kzalloc error\n");
  58                return -ENOMEM;
  59        }
  60        parser->buf_size = block_size;
  61
  62        /* extract fw versions */
  63        memcpy(fw_hdr, parser->file, sizeof(struct iwmct_fw_hdr));
  64        LOG_INFO(priv, FW_DOWNLOAD, "fw versions are:\n"
  65                "top %u.%u.%u gps %u.%u.%u bt %u.%u.%u tic %s\n",
  66                fw_hdr->top_major, fw_hdr->top_minor, fw_hdr->top_revision,
  67                fw_hdr->gps_major, fw_hdr->gps_minor, fw_hdr->gps_revision,
  68                fw_hdr->bt_major, fw_hdr->bt_minor, fw_hdr->bt_revision,
  69                fw_hdr->tic_name);
  70
  71        parser->cur_pos += sizeof(struct iwmct_fw_hdr);
  72
  73        LOG_TRACE(priv, FW_DOWNLOAD, "<--\n");
  74        return 0;
  75}
  76
  77static bool iwmct_checksum(struct iwmct_priv *priv)
  78{
  79        struct iwmct_parser *parser = &priv->parser;
  80        __le32 *file = (__le32 *)parser->file;
  81        int i, pad, steps;
  82        u32 accum = 0;
  83        u32 checksum;
  84        u32 mask = 0xffffffff;
  85
  86        pad = (parser->file_size - CHECKSUM_BYTES_NUM) % 4;
  87        steps =  (parser->file_size - CHECKSUM_BYTES_NUM) / 4;
  88
  89        LOG_INFO(priv, FW_DOWNLOAD, "pad=%d steps=%d\n", pad, steps);
  90
  91        for (i = 0; i < steps; i++)
  92                accum += le32_to_cpu(file[i]);
  93
  94        if (pad) {
  95                mask <<= 8 * (4 - pad);
  96                accum += le32_to_cpu(file[steps]) & mask;
  97        }
  98
  99        checksum = get_unaligned_le32((__le32 *)(parser->file +
 100                        parser->file_size - CHECKSUM_BYTES_NUM));
 101
 102        LOG_INFO(priv, FW_DOWNLOAD,
 103                "compare checksum accum=0x%x to checksum=0x%x\n",
 104                accum, checksum);
 105
 106        return checksum == accum;
 107}
 108
 109static int iwmct_parse_next_section(struct iwmct_priv *priv, const u8 **p_sec,
 110                                  size_t *sec_size, __le32 *sec_addr)
 111{
 112        struct iwmct_parser *parser = &priv->parser;
 113        struct iwmct_dbg *dbg = &priv->dbg;
 114        struct iwmct_fw_sec_hdr *sec_hdr;
 115
 116        LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
 117
 118        while (parser->cur_pos + sizeof(struct iwmct_fw_sec_hdr)
 119                <= parser->file_size) {
 120
 121                sec_hdr = (struct iwmct_fw_sec_hdr *)
 122                                (parser->file + parser->cur_pos);
 123                parser->cur_pos += sizeof(struct iwmct_fw_sec_hdr);
 124
 125                LOG_INFO(priv, FW_DOWNLOAD,
 126                        "sec hdr: type=%s addr=0x%x size=%d\n",
 127                        sec_hdr->type, sec_hdr->target_addr,
 128                        sec_hdr->data_size);
 129
 130                if (strcmp(sec_hdr->type, "ENT") == 0)
 131                        parser->entry_point = le32_to_cpu(sec_hdr->target_addr);
 132                else if (strcmp(sec_hdr->type, "LBL") == 0)
 133                        strcpy(dbg->label_fw, parser->file + parser->cur_pos);
 134                else if (((strcmp(sec_hdr->type, "TOP") == 0) &&
 135                          (priv->barker & BARKER_DNLOAD_TOP_MSK)) ||
 136                         ((strcmp(sec_hdr->type, "GPS") == 0) &&
 137                          (priv->barker & BARKER_DNLOAD_GPS_MSK)) ||
 138                         ((strcmp(sec_hdr->type, "BTH") == 0) &&
 139                          (priv->barker & BARKER_DNLOAD_BT_MSK))) {
 140                        *sec_addr = sec_hdr->target_addr;
 141                        *sec_size = le32_to_cpu(sec_hdr->data_size);
 142                        *p_sec = parser->file + parser->cur_pos;
 143                        parser->cur_pos += le32_to_cpu(sec_hdr->data_size);
 144                        return 1;
 145                } else if (strcmp(sec_hdr->type, "LOG") != 0)
 146                        LOG_WARNING(priv, FW_DOWNLOAD,
 147                                    "skipping section type %s\n",
 148                                    sec_hdr->type);
 149
 150                parser->cur_pos += le32_to_cpu(sec_hdr->data_size);
 151                LOG_INFO(priv, FW_DOWNLOAD,
 152                        "finished with section cur_pos=%zd\n", parser->cur_pos);
 153        }
 154
 155        LOG_TRACE(priv, INIT, "<--\n");
 156        return 0;
 157}
 158
 159static int iwmct_download_section(struct iwmct_priv *priv, const u8 *p_sec,
 160                                size_t sec_size, __le32 addr)
 161{
 162        struct iwmct_parser *parser = &priv->parser;
 163        struct iwmct_fw_load_hdr *hdr = (struct iwmct_fw_load_hdr *)parser->buf;
 164        const u8 *cur_block = p_sec;
 165        size_t sent = 0;
 166        int cnt = 0;
 167        int ret = 0;
 168        u32 cmd = 0;
 169
 170        LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
 171        LOG_INFO(priv, FW_DOWNLOAD, "Download address 0x%x size 0x%zx\n",
 172                                addr, sec_size);
 173
 174        while (sent < sec_size) {
 175                int i;
 176                u32 chksm = 0;
 177                u32 reset = atomic_read(&priv->reset);
 178                /* actual FW data */
 179                u32 data_size = min(parser->buf_size - sizeof(*hdr),
 180                                    sec_size - sent);
 181                /* Pad to block size */
 182                u32 trans_size = (data_size + sizeof(*hdr) +
 183                                  IWMC_SDIO_BLK_SIZE - 1) &
 184                                  ~(IWMC_SDIO_BLK_SIZE - 1);
 185                ++cnt;
 186
 187                /* in case of reset, interrupt FW DOWNLAOD */
 188                if (reset) {
 189                        LOG_INFO(priv, FW_DOWNLOAD,
 190                                 "Reset detected. Abort FW download!!!");
 191                        ret = -ECANCELED;
 192                        goto exit;
 193                }
 194
 195                memset(parser->buf, 0, parser->buf_size);
 196                cmd |= IWMC_OPCODE_WRITE << CMD_HDR_OPCODE_POS;
 197                cmd |= IWMC_CMD_SIGNATURE << CMD_HDR_SIGNATURE_POS;
 198                cmd |= (priv->dbg.direct ? 1 : 0) << CMD_HDR_DIRECT_ACCESS_POS;
 199                cmd |= (priv->dbg.checksum ? 1 : 0) << CMD_HDR_USE_CHECKSUM_POS;
 200                hdr->data_size = cpu_to_le32(data_size);
 201                hdr->target_addr = addr;
 202
 203                /* checksum is allowed for sizes divisible by 4 */
 204                if (data_size & 0x3)
 205                        cmd &= ~CMD_HDR_USE_CHECKSUM_MSK;
 206
 207                memcpy(hdr->data, cur_block, data_size);
 208
 209
 210                if (cmd & CMD_HDR_USE_CHECKSUM_MSK) {
 211
 212                        chksm = data_size + le32_to_cpu(addr) + cmd;
 213                        for (i = 0; i < data_size >> 2; i++)
 214                                chksm += ((u32 *)cur_block)[i];
 215
 216                        hdr->block_chksm = cpu_to_le32(chksm);
 217                        LOG_INFO(priv, FW_DOWNLOAD, "Checksum = 0x%X\n",
 218                                 hdr->block_chksm);
 219                }
 220
 221                LOG_INFO(priv, FW_DOWNLOAD, "trans#%d, len=%d, sent=%zd, "
 222                                "sec_size=%zd, startAddress 0x%X\n",
 223                                cnt, trans_size, sent, sec_size, addr);
 224
 225                if (priv->dbg.dump)
 226                        LOG_HEXDUMP(FW_DOWNLOAD, parser->buf, trans_size);
 227
 228
 229                hdr->cmd = cpu_to_le32(cmd);
 230                /* send it down */
 231                /* TODO: add more proper sending and error checking */
 232                ret = iwmct_tx(priv, parser->buf, trans_size);
 233                if (ret != 0) {
 234                        LOG_INFO(priv, FW_DOWNLOAD,
 235                                "iwmct_tx returned %d\n", ret);
 236                        goto exit;
 237                }
 238
 239                addr = cpu_to_le32(le32_to_cpu(addr) + data_size);
 240                sent += data_size;
 241                cur_block = p_sec + sent;
 242
 243                if (priv->dbg.blocks && (cnt + 1) >= priv->dbg.blocks) {
 244                        LOG_INFO(priv, FW_DOWNLOAD,
 245                                "Block number limit is reached [%d]\n",
 246                                priv->dbg.blocks);
 247                        break;
 248                }
 249        }
 250
 251        if (sent < sec_size)
 252                ret = -EINVAL;
 253exit:
 254        LOG_TRACE(priv, FW_DOWNLOAD, "<--\n");
 255        return ret;
 256}
 257
 258static int iwmct_kick_fw(struct iwmct_priv *priv, bool jump)
 259{
 260        struct iwmct_parser *parser = &priv->parser;
 261        struct iwmct_fw_load_hdr *hdr = (struct iwmct_fw_load_hdr *)parser->buf;
 262        int ret;
 263        u32 cmd;
 264
 265        LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
 266
 267        memset(parser->buf, 0, parser->buf_size);
 268        cmd = IWMC_CMD_SIGNATURE << CMD_HDR_SIGNATURE_POS;
 269        if (jump) {
 270                cmd |= IWMC_OPCODE_JUMP << CMD_HDR_OPCODE_POS;
 271                hdr->target_addr = cpu_to_le32(parser->entry_point);
 272                LOG_INFO(priv, FW_DOWNLOAD, "jump address 0x%x\n",
 273                                parser->entry_point);
 274        } else {
 275                cmd |= IWMC_OPCODE_LAST_COMMAND << CMD_HDR_OPCODE_POS;
 276                LOG_INFO(priv, FW_DOWNLOAD, "last command\n");
 277        }
 278
 279        hdr->cmd = cpu_to_le32(cmd);
 280
 281        LOG_HEXDUMP(FW_DOWNLOAD, parser->buf, sizeof(*hdr));
 282        /* send it down */
 283        /* TODO: add more proper sending and error checking */
 284        ret = iwmct_tx(priv, parser->buf, IWMC_SDIO_BLK_SIZE);
 285        if (ret)
 286                LOG_INFO(priv, FW_DOWNLOAD, "iwmct_tx returned %d", ret);
 287
 288        LOG_TRACE(priv, FW_DOWNLOAD, "<--\n");
 289        return 0;
 290}
 291
 292int iwmct_fw_load(struct iwmct_priv *priv)
 293{
 294        const u8 *fw_name = FW_NAME(FW_API_VER);
 295        const struct firmware *raw;
 296        const u8 *pdata;
 297        size_t len;
 298        __le32 addr;
 299        int ret;
 300
 301
 302        LOG_INFO(priv, FW_DOWNLOAD, "barker download request 0x%x is:\n",
 303                        priv->barker);
 304        LOG_INFO(priv, FW_DOWNLOAD, "*******  Top FW %s requested ********\n",
 305                        (priv->barker & BARKER_DNLOAD_TOP_MSK) ? "was" : "not");
 306        LOG_INFO(priv, FW_DOWNLOAD, "*******  GPS FW %s requested ********\n",
 307                        (priv->barker & BARKER_DNLOAD_GPS_MSK) ? "was" : "not");
 308        LOG_INFO(priv, FW_DOWNLOAD, "*******  BT FW %s requested ********\n",
 309                        (priv->barker & BARKER_DNLOAD_BT_MSK) ? "was" : "not");
 310
 311
 312        /* get the firmware */
 313        ret = request_firmware(&raw, fw_name, &priv->func->dev);
 314        if (ret < 0) {
 315                LOG_ERROR(priv, FW_DOWNLOAD, "%s request_firmware failed %d\n",
 316                          fw_name, ret);
 317                goto exit;
 318        }
 319
 320        if (raw->size < sizeof(struct iwmct_fw_sec_hdr)) {
 321                LOG_ERROR(priv, FW_DOWNLOAD, "%s smaller then (%zd) (%zd)\n",
 322                          fw_name, sizeof(struct iwmct_fw_sec_hdr), raw->size);
 323                goto exit;
 324        }
 325
 326        LOG_INFO(priv, FW_DOWNLOAD, "Read firmware '%s'\n", fw_name);
 327
 328        /* clear parser struct */
 329        ret = iwmct_fw_parser_init(priv, raw->data, raw->size, priv->trans_len);
 330        if (ret < 0) {
 331                LOG_ERROR(priv, FW_DOWNLOAD,
 332                          "iwmct_parser_init failed: Reason %d\n", ret);
 333                goto exit;
 334        }
 335
 336        if (!iwmct_checksum(priv)) {
 337                LOG_ERROR(priv, FW_DOWNLOAD, "checksum error\n");
 338                ret = -EINVAL;
 339                goto exit;
 340        }
 341
 342        /* download firmware to device */
 343        while (iwmct_parse_next_section(priv, &pdata, &len, &addr)) {
 344                ret = iwmct_download_section(priv, pdata, len, addr);
 345                if (ret) {
 346                        LOG_ERROR(priv, FW_DOWNLOAD,
 347                                  "%s download section failed\n", fw_name);
 348                        goto exit;
 349                }
 350        }
 351
 352        ret = iwmct_kick_fw(priv, !!(priv->barker & BARKER_DNLOAD_JUMP_MSK));
 353
 354exit:
 355        kfree(priv->parser.buf);
 356        release_firmware(raw);
 357        return ret;
 358}
 359