fw-download.c 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. #include <linux/firmware.h>
  27. #include <linux/mmc/sdio_func.h>
  28. #include <linux/slab.h>
  29. #include <asm/unaligned.h>
  30. #include "iwmc3200top.h"
  31. #include "log.h"
  32. #include "fw-msg.h"
  33. #define CHECKSUM_BYTES_NUM sizeof(u32)
  34. /**
  35. init parser struct with file
  36. */
  37. static int iwmct_fw_parser_init(struct iwmct_priv *priv, const u8 *file,
  38. size_t file_size, size_t block_size)
  39. {
  40. struct iwmct_parser *parser = &priv->parser;
  41. struct iwmct_fw_hdr *fw_hdr = &parser->versions;
  42. LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
  43. LOG_INFO(priv, FW_DOWNLOAD, "file_size=%zd\n", file_size);
  44. parser->file = file;
  45. parser->file_size = file_size;
  46. parser->cur_pos = 0;
  47. parser->entry_point = 0;
  48. parser->buf = kzalloc(block_size, GFP_KERNEL);
  49. if (!parser->buf) {
  50. LOG_ERROR(priv, FW_DOWNLOAD, "kzalloc error\n");
  51. return -ENOMEM;
  52. }
  53. parser->buf_size = block_size;
  54. /* extract fw versions */
  55. memcpy(fw_hdr, parser->file, sizeof(struct iwmct_fw_hdr));
  56. LOG_INFO(priv, FW_DOWNLOAD, "fw versions are:\n"
  57. "top %u.%u.%u gps %u.%u.%u bt %u.%u.%u tic %s\n",
  58. fw_hdr->top_major, fw_hdr->top_minor, fw_hdr->top_revision,
  59. fw_hdr->gps_major, fw_hdr->gps_minor, fw_hdr->gps_revision,
  60. fw_hdr->bt_major, fw_hdr->bt_minor, fw_hdr->bt_revision,
  61. fw_hdr->tic_name);
  62. parser->cur_pos += sizeof(struct iwmct_fw_hdr);
  63. LOG_TRACE(priv, FW_DOWNLOAD, "<--\n");
  64. return 0;
  65. }
  66. static bool iwmct_checksum(struct iwmct_priv *priv)
  67. {
  68. struct iwmct_parser *parser = &priv->parser;
  69. __le32 *file = (__le32 *)parser->file;
  70. int i, pad, steps;
  71. u32 accum = 0;
  72. u32 checksum;
  73. u32 mask = 0xffffffff;
  74. pad = (parser->file_size - CHECKSUM_BYTES_NUM) % 4;
  75. steps = (parser->file_size - CHECKSUM_BYTES_NUM) / 4;
  76. LOG_INFO(priv, FW_DOWNLOAD, "pad=%d steps=%d\n", pad, steps);
  77. for (i = 0; i < steps; i++)
  78. accum += le32_to_cpu(file[i]);
  79. if (pad) {
  80. mask <<= 8 * (4 - pad);
  81. accum += le32_to_cpu(file[steps]) & mask;
  82. }
  83. checksum = get_unaligned_le32((__le32 *)(parser->file +
  84. parser->file_size - CHECKSUM_BYTES_NUM));
  85. LOG_INFO(priv, FW_DOWNLOAD,
  86. "compare checksum accum=0x%x to checksum=0x%x\n",
  87. accum, checksum);
  88. return checksum == accum;
  89. }
  90. static int iwmct_parse_next_section(struct iwmct_priv *priv, const u8 **p_sec,
  91. size_t *sec_size, __le32 *sec_addr)
  92. {
  93. struct iwmct_parser *parser = &priv->parser;
  94. struct iwmct_dbg *dbg = &priv->dbg;
  95. struct iwmct_fw_sec_hdr *sec_hdr;
  96. LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
  97. while (parser->cur_pos + sizeof(struct iwmct_fw_sec_hdr)
  98. <= parser->file_size) {
  99. sec_hdr = (struct iwmct_fw_sec_hdr *)
  100. (parser->file + parser->cur_pos);
  101. parser->cur_pos += sizeof(struct iwmct_fw_sec_hdr);
  102. LOG_INFO(priv, FW_DOWNLOAD,
  103. "sec hdr: type=%s addr=0x%x size=%d\n",
  104. sec_hdr->type, sec_hdr->target_addr,
  105. sec_hdr->data_size);
  106. if (strcmp(sec_hdr->type, "ENT") == 0)
  107. parser->entry_point = le32_to_cpu(sec_hdr->target_addr);
  108. else if (strcmp(sec_hdr->type, "LBL") == 0)
  109. strcpy(dbg->label_fw, parser->file + parser->cur_pos);
  110. else if (((strcmp(sec_hdr->type, "TOP") == 0) &&
  111. (priv->barker & BARKER_DNLOAD_TOP_MSK)) ||
  112. ((strcmp(sec_hdr->type, "GPS") == 0) &&
  113. (priv->barker & BARKER_DNLOAD_GPS_MSK)) ||
  114. ((strcmp(sec_hdr->type, "BTH") == 0) &&
  115. (priv->barker & BARKER_DNLOAD_BT_MSK))) {
  116. *sec_addr = sec_hdr->target_addr;
  117. *sec_size = le32_to_cpu(sec_hdr->data_size);
  118. *p_sec = parser->file + parser->cur_pos;
  119. parser->cur_pos += le32_to_cpu(sec_hdr->data_size);
  120. return 1;
  121. } else if (strcmp(sec_hdr->type, "LOG") != 0)
  122. LOG_WARNING(priv, FW_DOWNLOAD,
  123. "skipping section type %s\n",
  124. sec_hdr->type);
  125. parser->cur_pos += le32_to_cpu(sec_hdr->data_size);
  126. LOG_INFO(priv, FW_DOWNLOAD,
  127. "finished with section cur_pos=%zd\n", parser->cur_pos);
  128. }
  129. LOG_TRACE(priv, INIT, "<--\n");
  130. return 0;
  131. }
  132. static int iwmct_download_section(struct iwmct_priv *priv, const u8 *p_sec,
  133. size_t sec_size, __le32 addr)
  134. {
  135. struct iwmct_parser *parser = &priv->parser;
  136. struct iwmct_fw_load_hdr *hdr = (struct iwmct_fw_load_hdr *)parser->buf;
  137. const u8 *cur_block = p_sec;
  138. size_t sent = 0;
  139. int cnt = 0;
  140. int ret = 0;
  141. u32 cmd = 0;
  142. LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
  143. LOG_INFO(priv, FW_DOWNLOAD, "Download address 0x%x size 0x%zx\n",
  144. addr, sec_size);
  145. while (sent < sec_size) {
  146. int i;
  147. u32 chksm = 0;
  148. u32 reset = atomic_read(&priv->reset);
  149. /* actual FW data */
  150. u32 data_size = min(parser->buf_size - sizeof(*hdr),
  151. sec_size - sent);
  152. /* Pad to block size */
  153. u32 trans_size = (data_size + sizeof(*hdr) +
  154. IWMC_SDIO_BLK_SIZE - 1) &
  155. ~(IWMC_SDIO_BLK_SIZE - 1);
  156. ++cnt;
  157. /* in case of reset, interrupt FW DOWNLAOD */
  158. if (reset) {
  159. LOG_INFO(priv, FW_DOWNLOAD,
  160. "Reset detected. Abort FW download!!!");
  161. ret = -ECANCELED;
  162. goto exit;
  163. }
  164. memset(parser->buf, 0, parser->buf_size);
  165. cmd |= IWMC_OPCODE_WRITE << CMD_HDR_OPCODE_POS;
  166. cmd |= IWMC_CMD_SIGNATURE << CMD_HDR_SIGNATURE_POS;
  167. cmd |= (priv->dbg.direct ? 1 : 0) << CMD_HDR_DIRECT_ACCESS_POS;
  168. cmd |= (priv->dbg.checksum ? 1 : 0) << CMD_HDR_USE_CHECKSUM_POS;
  169. hdr->data_size = cpu_to_le32(data_size);
  170. hdr->target_addr = addr;
  171. /* checksum is allowed for sizes divisible by 4 */
  172. if (data_size & 0x3)
  173. cmd &= ~CMD_HDR_USE_CHECKSUM_MSK;
  174. memcpy(hdr->data, cur_block, data_size);
  175. if (cmd & CMD_HDR_USE_CHECKSUM_MSK) {
  176. chksm = data_size + le32_to_cpu(addr) + cmd;
  177. for (i = 0; i < data_size >> 2; i++)
  178. chksm += ((u32 *)cur_block)[i];
  179. hdr->block_chksm = cpu_to_le32(chksm);
  180. LOG_INFO(priv, FW_DOWNLOAD, "Checksum = 0x%X\n",
  181. hdr->block_chksm);
  182. }
  183. LOG_INFO(priv, FW_DOWNLOAD, "trans#%d, len=%d, sent=%zd, "
  184. "sec_size=%zd, startAddress 0x%X\n",
  185. cnt, trans_size, sent, sec_size, addr);
  186. if (priv->dbg.dump)
  187. LOG_HEXDUMP(FW_DOWNLOAD, parser->buf, trans_size);
  188. hdr->cmd = cpu_to_le32(cmd);
  189. /* send it down */
  190. /* TODO: add more proper sending and error checking */
  191. ret = iwmct_tx(priv, parser->buf, trans_size);
  192. if (ret != 0) {
  193. LOG_INFO(priv, FW_DOWNLOAD,
  194. "iwmct_tx returned %d\n", ret);
  195. goto exit;
  196. }
  197. addr = cpu_to_le32(le32_to_cpu(addr) + data_size);
  198. sent += data_size;
  199. cur_block = p_sec + sent;
  200. if (priv->dbg.blocks && (cnt + 1) >= priv->dbg.blocks) {
  201. LOG_INFO(priv, FW_DOWNLOAD,
  202. "Block number limit is reached [%d]\n",
  203. priv->dbg.blocks);
  204. break;
  205. }
  206. }
  207. if (sent < sec_size)
  208. ret = -EINVAL;
  209. exit:
  210. LOG_TRACE(priv, FW_DOWNLOAD, "<--\n");
  211. return ret;
  212. }
  213. static int iwmct_kick_fw(struct iwmct_priv *priv, bool jump)
  214. {
  215. struct iwmct_parser *parser = &priv->parser;
  216. struct iwmct_fw_load_hdr *hdr = (struct iwmct_fw_load_hdr *)parser->buf;
  217. int ret;
  218. u32 cmd;
  219. LOG_TRACE(priv, FW_DOWNLOAD, "-->\n");
  220. memset(parser->buf, 0, parser->buf_size);
  221. cmd = IWMC_CMD_SIGNATURE << CMD_HDR_SIGNATURE_POS;
  222. if (jump) {
  223. cmd |= IWMC_OPCODE_JUMP << CMD_HDR_OPCODE_POS;
  224. hdr->target_addr = cpu_to_le32(parser->entry_point);
  225. LOG_INFO(priv, FW_DOWNLOAD, "jump address 0x%x\n",
  226. parser->entry_point);
  227. } else {
  228. cmd |= IWMC_OPCODE_LAST_COMMAND << CMD_HDR_OPCODE_POS;
  229. LOG_INFO(priv, FW_DOWNLOAD, "last command\n");
  230. }
  231. hdr->cmd = cpu_to_le32(cmd);
  232. LOG_HEXDUMP(FW_DOWNLOAD, parser->buf, sizeof(*hdr));
  233. /* send it down */
  234. /* TODO: add more proper sending and error checking */
  235. ret = iwmct_tx(priv, parser->buf, IWMC_SDIO_BLK_SIZE);
  236. if (ret)
  237. LOG_INFO(priv, FW_DOWNLOAD, "iwmct_tx returned %d", ret);
  238. LOG_TRACE(priv, FW_DOWNLOAD, "<--\n");
  239. return 0;
  240. }
  241. int iwmct_fw_load(struct iwmct_priv *priv)
  242. {
  243. const u8 *fw_name = FW_NAME(FW_API_VER);
  244. const struct firmware *raw;
  245. const u8 *pdata;
  246. size_t len;
  247. __le32 addr;
  248. int ret;
  249. LOG_INFO(priv, FW_DOWNLOAD, "barker download request 0x%x is:\n",
  250. priv->barker);
  251. LOG_INFO(priv, FW_DOWNLOAD, "******* Top FW %s requested ********\n",
  252. (priv->barker & BARKER_DNLOAD_TOP_MSK) ? "was" : "not");
  253. LOG_INFO(priv, FW_DOWNLOAD, "******* GPS FW %s requested ********\n",
  254. (priv->barker & BARKER_DNLOAD_GPS_MSK) ? "was" : "not");
  255. LOG_INFO(priv, FW_DOWNLOAD, "******* BT FW %s requested ********\n",
  256. (priv->barker & BARKER_DNLOAD_BT_MSK) ? "was" : "not");
  257. /* get the firmware */
  258. ret = request_firmware(&raw, fw_name, &priv->func->dev);
  259. if (ret < 0) {
  260. LOG_ERROR(priv, FW_DOWNLOAD, "%s request_firmware failed %d\n",
  261. fw_name, ret);
  262. goto exit;
  263. }
  264. if (raw->size < sizeof(struct iwmct_fw_sec_hdr)) {
  265. LOG_ERROR(priv, FW_DOWNLOAD, "%s smaller then (%zd) (%zd)\n",
  266. fw_name, sizeof(struct iwmct_fw_sec_hdr), raw->size);
  267. goto exit;
  268. }
  269. LOG_INFO(priv, FW_DOWNLOAD, "Read firmware '%s'\n", fw_name);
  270. /* clear parser struct */
  271. ret = iwmct_fw_parser_init(priv, raw->data, raw->size, priv->trans_len);
  272. if (ret < 0) {
  273. LOG_ERROR(priv, FW_DOWNLOAD,
  274. "iwmct_parser_init failed: Reason %d\n", ret);
  275. goto exit;
  276. }
  277. if (!iwmct_checksum(priv)) {
  278. LOG_ERROR(priv, FW_DOWNLOAD, "checksum error\n");
  279. ret = -EINVAL;
  280. goto exit;
  281. }
  282. /* download firmware to device */
  283. while (iwmct_parse_next_section(priv, &pdata, &len, &addr)) {
  284. ret = iwmct_download_section(priv, pdata, len, addr);
  285. if (ret) {
  286. LOG_ERROR(priv, FW_DOWNLOAD,
  287. "%s download section failed\n", fw_name);
  288. goto exit;
  289. }
  290. }
  291. ret = iwmct_kick_fw(priv, !!(priv->barker & BARKER_DNLOAD_JUMP_MSK));
  292. exit:
  293. kfree(priv->parser.buf);
  294. release_firmware(raw);
  295. return ret;
  296. }