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