fw-download.c 9.5 KB

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