dfu_mtd.c 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * dfu_mtd.c -- DFU for MTD device.
  4. *
  5. * Copyright (C) 2019,STMicroelectronics - All Rights Reserved
  6. *
  7. * Based on dfu_nand.c
  8. */
  9. #include <common.h>
  10. #include <dfu.h>
  11. #include <mtd.h>
  12. #include <linux/err.h>
  13. #include <linux/ctype.h>
  14. static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
  15. {
  16. return !do_div(size, mtd->erasesize);
  17. }
  18. /* Logic taken from cmd/mtd.c:mtd_oob_write_is_empty() */
  19. static bool mtd_page_is_empty(struct mtd_oob_ops *op)
  20. {
  21. int i;
  22. for (i = 0; i < op->len; i++)
  23. if (op->datbuf[i] != 0xff)
  24. return false;
  25. /* oob is not used, with MTD_OPS_AUTO_OOB & ooblen=0 */
  26. return true;
  27. }
  28. static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
  29. u64 offset, void *buf, long *len)
  30. {
  31. u64 off, lim, remaining, lock_ofs, lock_len;
  32. struct mtd_info *mtd = dfu->data.mtd.info;
  33. struct mtd_oob_ops io_op = {};
  34. int ret = 0;
  35. bool has_pages = mtd->type == MTD_NANDFLASH ||
  36. mtd->type == MTD_MLCNANDFLASH;
  37. /* if buf == NULL return total size of the area */
  38. if (!buf) {
  39. *len = dfu->data.mtd.size;
  40. return 0;
  41. }
  42. off = lock_ofs = dfu->data.mtd.start + offset + dfu->bad_skip;
  43. lim = dfu->data.mtd.start + dfu->data.mtd.size;
  44. if (off >= lim) {
  45. printf("Limit reached 0x%llx\n", lim);
  46. *len = 0;
  47. return op == DFU_OP_READ ? 0 : -EIO;
  48. }
  49. /* limit request with the available size */
  50. if (off + *len >= lim)
  51. *len = lim - off;
  52. if (!mtd_is_aligned_with_block_size(mtd, off)) {
  53. printf("Offset not aligned with a block (0x%x)\n",
  54. mtd->erasesize);
  55. return 0;
  56. }
  57. /* first erase */
  58. if (op == DFU_OP_WRITE) {
  59. struct erase_info erase_op = {};
  60. remaining = lock_len = round_up(*len, mtd->erasesize);
  61. erase_op.mtd = mtd;
  62. erase_op.addr = off;
  63. erase_op.len = mtd->erasesize;
  64. erase_op.scrub = 0;
  65. debug("Unlocking the mtd device\n");
  66. ret = mtd_unlock(mtd, lock_ofs, lock_len);
  67. if (ret && ret != -EOPNOTSUPP) {
  68. printf("MTD device unlock failed\n");
  69. return 0;
  70. }
  71. while (remaining) {
  72. if (erase_op.addr + remaining > lim) {
  73. printf("Limit reached 0x%llx while erasing at offset 0x%llx\n",
  74. lim, off);
  75. return -EIO;
  76. }
  77. ret = mtd_erase(mtd, &erase_op);
  78. if (ret) {
  79. /* Abort if its not a bad block error */
  80. if (ret != -EIO) {
  81. printf("Failure while erasing at offset 0x%llx\n",
  82. erase_op.fail_addr);
  83. return 0;
  84. }
  85. printf("Skipping bad block at 0x%08llx\n",
  86. erase_op.addr);
  87. } else {
  88. remaining -= mtd->erasesize;
  89. }
  90. /* Continue erase behind bad block */
  91. erase_op.addr += mtd->erasesize;
  92. }
  93. }
  94. io_op.mode = MTD_OPS_AUTO_OOB;
  95. io_op.len = *len;
  96. if (has_pages && io_op.len > mtd->writesize)
  97. io_op.len = mtd->writesize;
  98. io_op.ooblen = 0;
  99. io_op.datbuf = buf;
  100. io_op.oobbuf = NULL;
  101. /* Loop over to do the actual read/write */
  102. remaining = *len;
  103. while (remaining) {
  104. if (off + remaining > lim) {
  105. printf("Limit reached 0x%llx while %s at offset 0x%llx\n",
  106. lim, op == DFU_OP_READ ? "reading" : "writing",
  107. off);
  108. if (op == DFU_OP_READ) {
  109. *len -= remaining;
  110. return 0;
  111. } else {
  112. return -EIO;
  113. }
  114. }
  115. /* Skip the block if it is bad */
  116. if (mtd_is_aligned_with_block_size(mtd, off) &&
  117. mtd_block_isbad(mtd, off)) {
  118. off += mtd->erasesize;
  119. dfu->bad_skip += mtd->erasesize;
  120. continue;
  121. }
  122. if (op == DFU_OP_READ)
  123. ret = mtd_read_oob(mtd, off, &io_op);
  124. else if (has_pages && dfu->data.mtd.ubi && mtd_page_is_empty(&io_op)) {
  125. /* in case of ubi partition, do not write an empty page, only skip it */
  126. ret = 0;
  127. io_op.retlen = mtd->writesize;
  128. io_op.oobretlen = mtd->oobsize;
  129. } else {
  130. ret = mtd_write_oob(mtd, off, &io_op);
  131. }
  132. if (ret) {
  133. printf("Failure while %s at offset 0x%llx\n",
  134. op == DFU_OP_READ ? "reading" : "writing", off);
  135. return -EIO;
  136. }
  137. off += io_op.retlen;
  138. remaining -= io_op.retlen;
  139. io_op.datbuf += io_op.retlen;
  140. io_op.len = remaining;
  141. if (has_pages && io_op.len > mtd->writesize)
  142. io_op.len = mtd->writesize;
  143. }
  144. if (op == DFU_OP_WRITE) {
  145. /* Write done, lock again */
  146. debug("Locking the mtd device\n");
  147. ret = mtd_lock(mtd, lock_ofs, lock_len);
  148. if (ret == -EOPNOTSUPP)
  149. ret = 0;
  150. else if (ret)
  151. printf("MTD device lock failed\n");
  152. }
  153. return ret;
  154. }
  155. static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
  156. {
  157. *size = dfu->data.mtd.info->size;
  158. return 0;
  159. }
  160. static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
  161. long *len)
  162. {
  163. int ret = -1;
  164. switch (dfu->layout) {
  165. case DFU_RAW_ADDR:
  166. ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
  167. break;
  168. default:
  169. printf("%s: Layout (%s) not (yet) supported!\n", __func__,
  170. dfu_get_layout(dfu->layout));
  171. }
  172. return ret;
  173. }
  174. static int dfu_write_medium_mtd(struct dfu_entity *dfu,
  175. u64 offset, void *buf, long *len)
  176. {
  177. int ret = -1;
  178. switch (dfu->layout) {
  179. case DFU_RAW_ADDR:
  180. ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
  181. break;
  182. default:
  183. printf("%s: Layout (%s) not (yet) supported!\n", __func__,
  184. dfu_get_layout(dfu->layout));
  185. }
  186. return ret;
  187. }
  188. static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
  189. {
  190. struct mtd_info *mtd = dfu->data.mtd.info;
  191. u64 remaining;
  192. int ret;
  193. /* in case of ubi partition, erase rest of the partition */
  194. if (dfu->data.mtd.ubi) {
  195. struct erase_info erase_op = {};
  196. erase_op.mtd = dfu->data.mtd.info;
  197. erase_op.addr = round_up(dfu->data.mtd.start + dfu->offset +
  198. dfu->bad_skip, mtd->erasesize);
  199. erase_op.len = mtd->erasesize;
  200. erase_op.scrub = 0;
  201. remaining = dfu->data.mtd.start + dfu->data.mtd.size -
  202. erase_op.addr;
  203. while (remaining) {
  204. ret = mtd_erase(mtd, &erase_op);
  205. if (ret) {
  206. /* Abort if its not a bad block error */
  207. if (ret != -EIO)
  208. break;
  209. printf("Skipping bad block at 0x%08llx\n",
  210. erase_op.addr);
  211. }
  212. /* Skip bad block and continue behind it */
  213. erase_op.addr += mtd->erasesize;
  214. remaining -= mtd->erasesize;
  215. }
  216. }
  217. return 0;
  218. }
  219. static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
  220. {
  221. /*
  222. * Currently, Poll Timeout != 0 is only needed on nand
  223. * ubi partition, as sectors which are not used need
  224. * to be erased
  225. */
  226. if (dfu->data.mtd.ubi)
  227. return DFU_MANIFEST_POLL_TIMEOUT;
  228. return DFU_DEFAULT_POLL_TIMEOUT;
  229. }
  230. int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char **argv, int argc)
  231. {
  232. char *s;
  233. struct mtd_info *mtd;
  234. int part;
  235. mtd = get_mtd_device_nm(devstr);
  236. if (IS_ERR_OR_NULL(mtd))
  237. return -ENODEV;
  238. put_mtd_device(mtd);
  239. dfu->dev_type = DFU_DEV_MTD;
  240. dfu->data.mtd.info = mtd;
  241. dfu->max_buf_size = mtd->erasesize;
  242. if (argc < 1)
  243. return -EINVAL;
  244. if (!strcmp(argv[0], "raw")) {
  245. if (argc != 3)
  246. return -EINVAL;
  247. dfu->layout = DFU_RAW_ADDR;
  248. dfu->data.mtd.start = hextoul(argv[1], &s);
  249. if (*s)
  250. return -EINVAL;
  251. dfu->data.mtd.size = hextoul(argv[2], &s);
  252. if (*s)
  253. return -EINVAL;
  254. } else if ((!strcmp(argv[0], "part")) || (!strcmp(argv[0], "partubi"))) {
  255. struct mtd_info *partition;
  256. int partnum = 0;
  257. bool part_found = false;
  258. if (argc != 2)
  259. return -EINVAL;
  260. dfu->layout = DFU_RAW_ADDR;
  261. part = dectoul(argv[1], &s);
  262. if (*s)
  263. return -EINVAL;
  264. /* register partitions with MTDIDS/MTDPARTS or OF fallback */
  265. mtd_probe_devices();
  266. partnum = 0;
  267. list_for_each_entry(partition, &mtd->partitions, node) {
  268. partnum++;
  269. if (partnum == part) {
  270. part_found = true;
  271. break;
  272. }
  273. }
  274. if (!part_found) {
  275. printf("No partition %d in %s\n", part, mtd->name);
  276. return -1;
  277. }
  278. log_debug("partition %d:%s in %s\n", partnum, partition->name, mtd->name);
  279. dfu->data.mtd.start = partition->offset;
  280. dfu->data.mtd.size = partition->size;
  281. if (!strcmp(argv[0], "partubi"))
  282. dfu->data.mtd.ubi = 1;
  283. } else {
  284. printf("%s: Memory layout (%s) not supported!\n", __func__, argv[0]);
  285. return -1;
  286. }
  287. if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
  288. printf("Offset not aligned with a block (0x%x)\n",
  289. mtd->erasesize);
  290. return -EINVAL;
  291. }
  292. if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
  293. printf("Size not aligned with a block (0x%x)\n",
  294. mtd->erasesize);
  295. return -EINVAL;
  296. }
  297. dfu->get_medium_size = dfu_get_medium_size_mtd;
  298. dfu->read_medium = dfu_read_medium_mtd;
  299. dfu->write_medium = dfu_write_medium_mtd;
  300. dfu->flush_medium = dfu_flush_medium_mtd;
  301. dfu->poll_timeout = dfu_polltimeout_mtd;
  302. /* initial state */
  303. dfu->inited = 0;
  304. return 0;
  305. }