linux_mtd.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. /*
  2. * This file is part of the flashrom project.
  3. *
  4. * Copyright 2015 Google Inc.
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; version 2 of the License.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #include <ctype.h>
  20. #include <errno.h>
  21. #include <fcntl.h>
  22. #include <inttypes.h>
  23. #include <libgen.h>
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <mtd/mtd-user.h>
  27. #include <string.h>
  28. #include <sys/ioctl.h>
  29. #include <sys/stat.h>
  30. #include <unistd.h>
  31. #include "file.h"
  32. #include "flash.h"
  33. #include "programmer.h"
  34. #include "writeprotect.h"
  35. #define LINUX_DEV_ROOT "/dev"
  36. #define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd"
  37. /* enough space for LINUX_MTD_SYSFS_ROOT + directory name + filename */
  38. static char sysfs_path[PATH_MAX];
  39. static int dev_fd = -1;
  40. static int mtd_device_is_writeable;
  41. /* Size info is presented in bytes in sysfs. */
  42. static unsigned long int mtd_total_size;
  43. static unsigned long int mtd_numeraseregions;
  44. static unsigned long int mtd_erasesize; /* only valid if numeraseregions is 0 */
  45. static struct wp wp_mtd; /* forward declaration */
  46. static int stat_mtd_files(char *dev_path)
  47. {
  48. struct stat s;
  49. errno = 0;
  50. if (stat(dev_path, &s) < 0) {
  51. msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
  52. return 1;
  53. }
  54. if (lstat(sysfs_path, &s) < 0) {
  55. msg_pdbg("Cannot stat \"%s\" : %s\n",
  56. sysfs_path, strerror(errno));
  57. return 1;
  58. }
  59. return 0;
  60. }
  61. /* read a string from a sysfs file and sanitize it */
  62. static int read_sysfs_string(const char *filename, char *buf, int len)
  63. {
  64. int fd, bytes_read, i;
  65. char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
  66. snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
  67. if ((fd = open(path, O_RDONLY)) < 0) {
  68. msg_perr("Cannot open %s\n", path);
  69. return 1;
  70. }
  71. if ((bytes_read = read(fd, buf, len - 1)) < 0) {
  72. msg_perr("Cannot read %s\n", path);
  73. close(fd);
  74. return 1;
  75. }
  76. buf[bytes_read] = '\0';
  77. /*
  78. * Files from sysfs sometimes contain a newline or other garbage that
  79. * can confuse functions like strtoul() and ruin formatting in print
  80. * statements. Replace the first non-printable character (space is
  81. * considered printable) with a proper string terminator.
  82. */
  83. for (i = 0; i < len; i++) {
  84. if (!isprint(buf[i])) {
  85. buf[i] = '\0';
  86. break;
  87. }
  88. }
  89. close(fd);
  90. return 0;
  91. }
  92. static int read_sysfs_int(const char *filename, unsigned long int *val)
  93. {
  94. char buf[32];
  95. char *endptr;
  96. if (read_sysfs_string(filename, buf, sizeof(buf)))
  97. return 1;
  98. errno = 0;
  99. *val = strtoul(buf, &endptr, 0);
  100. if (endptr != &buf[strlen(buf)]) {
  101. msg_perr("Error reading %s\n", filename);
  102. return 1;
  103. }
  104. if (errno) {
  105. msg_perr("Error reading %s: %s\n", filename, strerror(errno));
  106. return 1;
  107. }
  108. return 0;
  109. }
  110. /* returns 0 to indicate success, non-zero to indicate error */
  111. static int get_mtd_info(void)
  112. {
  113. unsigned long int tmp;
  114. char mtd_device_name[32];
  115. /* Flags */
  116. if (read_sysfs_int("flags", &tmp))
  117. return 1;
  118. if (tmp & MTD_WRITEABLE) {
  119. /* cache for later use by write function */
  120. mtd_device_is_writeable = 1;
  121. }
  122. /* Device name */
  123. if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
  124. return 1;
  125. /* Total size */
  126. if (read_sysfs_int("size", &mtd_total_size))
  127. return 1;
  128. if (__builtin_popcount(mtd_total_size) != 1) {
  129. msg_perr("MTD size is not a power of 2\n");
  130. return 1;
  131. }
  132. /* Erase size */
  133. if (read_sysfs_int("erasesize", &mtd_erasesize))
  134. return 1;
  135. if (__builtin_popcount(mtd_erasesize) != 1) {
  136. msg_perr("MTD erase size is not a power of 2\n");
  137. return 1;
  138. }
  139. /* Erase regions */
  140. if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
  141. return 1;
  142. if (mtd_numeraseregions != 0) {
  143. msg_perr("Non-uniform eraseblock size is unsupported.\n");
  144. return 1;
  145. }
  146. msg_pspew("%s: device_name: \"%s\", is_writeable: %d, "
  147. "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
  148. __func__, mtd_device_name, mtd_device_is_writeable,
  149. mtd_numeraseregions, mtd_total_size, mtd_erasesize);
  150. return 0;
  151. }
  152. static int linux_mtd_probe(struct flashctx *flash)
  153. {
  154. flash->wp = &wp_mtd;
  155. flash->tested = TEST_OK_PREW;
  156. flash->total_size = mtd_total_size / 1024; /* bytes -> kB */
  157. flash->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
  158. flash->block_erasers[0].eraseblocks[0].count =
  159. mtd_total_size / mtd_erasesize;
  160. return 1;
  161. }
  162. static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
  163. unsigned int start, unsigned int len)
  164. {
  165. unsigned int eb_size = flash->block_erasers[0].eraseblocks[0].size;
  166. unsigned int i;
  167. if (lseek(dev_fd, start, SEEK_SET) != start) {
  168. msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
  169. return 1;
  170. }
  171. for (i = 0; i < len; ) {
  172. /* Try to align reads to eraseblock size */
  173. unsigned int step = eb_size - ((start + i) % eb_size);
  174. step = min(step, len - i);
  175. if (read(dev_fd, buf + i, step) != step) {
  176. msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
  177. step, start + i, strerror(errno));
  178. return 1;
  179. }
  180. i += step;
  181. }
  182. return 0;
  183. }
  184. /* this version assumes we must divide the write request into pages ourselves */
  185. static int linux_mtd_write(struct flashctx *flash, uint8_t *buf,
  186. unsigned int start, unsigned int len)
  187. {
  188. unsigned int page;
  189. unsigned int chunksize, page_size;
  190. chunksize = page_size = flash->page_size;
  191. if (!mtd_device_is_writeable)
  192. return 1;
  193. for (page = start / page_size;
  194. page <= (start + len - 1) / page_size; page++) {
  195. unsigned int i, starthere, lenhere;
  196. starthere = max(start, page * page_size);
  197. lenhere = min(start + len, (page + 1) * page_size) - starthere;
  198. for (i = 0; i < lenhere; i += chunksize) {
  199. unsigned int towrite = min(chunksize, lenhere - i);
  200. if (lseek(dev_fd, starthere, SEEK_SET) != starthere) {
  201. msg_perr("Cannot seek to 0x%06x: %s\n",
  202. start, strerror(errno));
  203. return 1;
  204. }
  205. if (write(dev_fd, &buf[starthere - start], towrite) != towrite) {
  206. msg_perr("Cannot read 0x%06x bytes at 0x%06x: "
  207. "%s\n", start, len, strerror(errno));
  208. return 1;
  209. }
  210. }
  211. }
  212. return 0;
  213. }
  214. static int linux_mtd_erase(struct flashctx *flash,
  215. unsigned int start, unsigned int len)
  216. {
  217. uint32_t u;
  218. if (mtd_numeraseregions != 0) {
  219. /* TODO: Support non-uniform eraseblock size using
  220. use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
  221. }
  222. for (u = 0; u < len; u += mtd_erasesize) {
  223. struct erase_info_user erase_info = {
  224. .start = start + u,
  225. .length = mtd_erasesize,
  226. };
  227. if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
  228. msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
  229. return 1;
  230. }
  231. }
  232. return 0;
  233. }
  234. static struct opaque_programmer programmer_linux_mtd = {
  235. /* max_data_{read,write} don't have any effect for this programmer */
  236. .max_data_read = MAX_DATA_UNSPECIFIED,
  237. .max_data_write = MAX_DATA_UNSPECIFIED,
  238. .probe = linux_mtd_probe,
  239. .read = linux_mtd_read,
  240. .write = linux_mtd_write,
  241. .erase = linux_mtd_erase,
  242. };
  243. /* Returns 0 if setup is successful, non-zero to indicate error */
  244. static int linux_mtd_setup(int dev_num)
  245. {
  246. char dev_path[16]; /* "/dev/mtdN" */
  247. int ret = 1;
  248. if (dev_num < 0) {
  249. char *tmp, *p;
  250. tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
  251. if (!tmp) {
  252. msg_pdbg("%s: NOR type device not found.\n", __func__);
  253. goto linux_mtd_setup_exit;
  254. }
  255. /* "tmp" should be something like "/sys/blah/mtdN/type" */
  256. p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
  257. while (p[0] == '/')
  258. p++;
  259. if (sscanf(p, "mtd%d", &dev_num) != 1) {
  260. msg_perr("Can't obtain device number from \"%s\"\n", p);
  261. free(tmp);
  262. goto linux_mtd_setup_exit;
  263. }
  264. free(tmp);
  265. }
  266. snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
  267. LINUX_MTD_SYSFS_ROOT, dev_num);
  268. snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
  269. LINUX_DEV_ROOT, dev_num);
  270. msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
  271. __func__, sysfs_path, dev_path);
  272. if (stat_mtd_files(dev_path))
  273. goto linux_mtd_setup_exit;
  274. if (get_mtd_info())
  275. goto linux_mtd_setup_exit;
  276. if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
  277. msg_pdbg("%s: failed to open %s: %s\n", __func__,
  278. dev_path, strerror(errno));
  279. goto linux_mtd_setup_exit;
  280. }
  281. ret = 0;
  282. linux_mtd_setup_exit:
  283. return ret;
  284. }
  285. static int linux_mtd_shutdown(void *data)
  286. {
  287. if (dev_fd != -1) {
  288. close(dev_fd);
  289. dev_fd = -1;
  290. }
  291. return 0;
  292. }
  293. int linux_mtd_init(void)
  294. {
  295. char *param;
  296. int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
  297. int ret = 1;
  298. if (alias && alias->type != ALIAS_HOST)
  299. return 1;
  300. param = extract_programmer_param("dev");
  301. if (param) {
  302. char *endptr;
  303. dev_num = strtol(param, &endptr, 0);
  304. if ((param == endptr) || (dev_num < 0)) {
  305. msg_perr("Invalid device number %s. Use flashrom -p "
  306. "linux_mtd:dev=N where N is a valid MTD "
  307. "device number\n", param);
  308. goto linux_mtd_init_exit;
  309. }
  310. }
  311. if (linux_mtd_setup(dev_num))
  312. goto linux_mtd_init_exit;
  313. if (register_shutdown(linux_mtd_shutdown, NULL))
  314. goto linux_mtd_init_exit;
  315. register_opaque_programmer(&programmer_linux_mtd);
  316. ret = 0;
  317. linux_mtd_init_exit:
  318. msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
  319. return ret;
  320. }
  321. /*
  322. * Write-protect functions.
  323. */
  324. static int mtd_wp_list_ranges(const struct flashctx *flash)
  325. {
  326. /* TODO: implement this */
  327. msg_perr("--wp-list is not currently implemented for MTD.\n");
  328. return 1;
  329. }
  330. /*
  331. * We only have MEMLOCK to enable write-protection for a particular block,
  332. * so we need to do force the user to use --wp-range and --wp-enable
  333. * command-line arguments simultaneously. (Fortunately, CrOS factory
  334. * installer does this already).
  335. *
  336. * The --wp-range argument is processed first and will set these variables
  337. * which --wp-enable will use afterward.
  338. */
  339. static unsigned int wp_range_start;
  340. static unsigned int wp_range_len;
  341. static int wp_set_range_called = 0;
  342. static int mtd_wp_set_range(const struct flashctx *flash,
  343. unsigned int start, unsigned int len)
  344. {
  345. wp_range_start = start;
  346. wp_range_len = len;
  347. wp_set_range_called = 1;
  348. return 0;
  349. }
  350. static int mtd_wp_enable_writeprotect(const struct flashctx *flash, enum wp_mode mode)
  351. {
  352. struct erase_info_user entire_chip = {
  353. .start = 0,
  354. .length = mtd_total_size,
  355. };
  356. struct erase_info_user desired_range = {
  357. .start = wp_range_start,
  358. .length = wp_range_len,
  359. };
  360. if (!wp_set_range_called) {
  361. msg_perr("For MTD, --wp-range and --wp-enable must be "
  362. "used simultaneously.\n");
  363. return 1;
  364. }
  365. /*
  366. * MTD handles write-protection additively, so whatever new range is
  367. * specified is added to the range which is currently protected. To be
  368. * consistent with flashrom behavior with other programmer interfaces,
  369. * we need to disable the current write protection and then enable
  370. * it for the desired range.
  371. */
  372. if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
  373. msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
  374. __func__, strerror(errno));
  375. msg_perr("Did you disable WP#?\n");
  376. return 1;
  377. }
  378. if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
  379. msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
  380. __func__, strerror(errno));
  381. return 1;
  382. }
  383. return 0;
  384. }
  385. static int mtd_wp_disable_writeprotect(const struct flashctx *flash)
  386. {
  387. struct erase_info_user erase_info;
  388. if (wp_set_range_called) {
  389. erase_info.start = wp_range_start;
  390. erase_info.length = wp_range_len;
  391. } else {
  392. erase_info.start = 0;
  393. erase_info.length = mtd_total_size;
  394. }
  395. if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
  396. msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
  397. msg_perr("Did you disable WP#?\n");
  398. return 1;
  399. }
  400. return 0;
  401. }
  402. static int mtd_wp_status(const struct flashctx *flash)
  403. {
  404. uint32_t start = 0, len = 0;
  405. int start_found = 0;
  406. unsigned int u;
  407. /* For now, assume only one contiguous region can be locked (NOR) */
  408. /* FIXME: use flash struct members instead of raw MTD values here */
  409. for (u = 0; u < mtd_total_size; u += mtd_erasesize) {
  410. int rc;
  411. struct erase_info_user erase_info = {
  412. .start = u,
  413. .length = mtd_erasesize,
  414. };
  415. rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
  416. if (rc < 0) {
  417. msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
  418. return 1;
  419. } else if (rc == 1) {
  420. if (!start_found) {
  421. start = erase_info.start;
  422. start_found = 1;
  423. }
  424. len += mtd_erasesize;
  425. } else if (rc == 0) {
  426. if (start_found) {
  427. /* TODO: changes required for supporting non-contiguous locked regions */
  428. break;
  429. }
  430. }
  431. }
  432. msg_cinfo("WP: write protect is %s.\n",
  433. start_found ? "enabled": "disabled");
  434. msg_pinfo("WP: write protect range: start=0x%08x, "
  435. "len=0x%08x\n", start, len);
  436. return 0;
  437. }
  438. static struct wp wp_mtd = {
  439. .list_ranges = mtd_wp_list_ranges,
  440. .set_range = mtd_wp_set_range,
  441. .enable = mtd_wp_enable_writeprotect,
  442. .disable = mtd_wp_disable_writeprotect,
  443. .wp_status = mtd_wp_status,
  444. };