blob: 537dcbecc5706710cad3e51659753d837b949af2 [file] [log] [blame]
David Hendrickscebee892015-05-23 20:30:30 -07001/*
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
20#include <ctype.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <inttypes.h>
24#include <libgen.h>
Gwendal Grignoudb133582018-03-23 12:28:20 -070025#include <limits.h>
David Hendrickscebee892015-05-23 20:30:30 -070026#include <stdio.h>
27#include <stdlib.h>
28#include <mtd/mtd-user.h>
29#include <string.h>
30#include <sys/ioctl.h>
31#include <sys/stat.h>
32#include <unistd.h>
33
34#include "file.h"
35#include "flash.h"
36#include "programmer.h"
David Hendricks85f61c52015-06-04 19:52:59 -070037#include "writeprotect.h"
David Hendrickscebee892015-05-23 20:30:30 -070038
39#define LINUX_DEV_ROOT "/dev"
40#define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd"
41
42/* enough space for LINUX_MTD_SYSFS_ROOT + directory name + filename */
43static char sysfs_path[PATH_MAX];
44
45static int dev_fd = -1;
46
David Hendrickscebee892015-05-23 20:30:30 -070047static int mtd_device_is_writeable;
48
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -070049static int mtd_no_erase;
50
David Hendrickscebee892015-05-23 20:30:30 -070051/* Size info is presented in bytes in sysfs. */
52static unsigned long int mtd_total_size;
53static unsigned long int mtd_numeraseregions;
54static unsigned long int mtd_erasesize; /* only valid if numeraseregions is 0 */
55
David Hendricks85f61c52015-06-04 19:52:59 -070056static struct wp wp_mtd; /* forward declaration */
57
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -070058static int stat_mtd_files(char *dev_path)
David Hendrickscebee892015-05-23 20:30:30 -070059{
60 struct stat s;
61
62 errno = 0;
63 if (stat(dev_path, &s) < 0) {
64 msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
65 return 1;
66 }
67
68 if (lstat(sysfs_path, &s) < 0) {
69 msg_pdbg("Cannot stat \"%s\" : %s\n",
70 sysfs_path, strerror(errno));
71 return 1;
72 }
73
74 return 0;
75}
76
77/* read a string from a sysfs file and sanitize it */
78static int read_sysfs_string(const char *filename, char *buf, int len)
79{
80 int fd, bytes_read, i;
81 char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
82
83 snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
84
85 if ((fd = open(path, O_RDONLY)) < 0) {
86 msg_perr("Cannot open %s\n", path);
87 return 1;
88 }
89
90 if ((bytes_read = read(fd, buf, len - 1)) < 0) {
91 msg_perr("Cannot read %s\n", path);
92 close(fd);
93 return 1;
94 }
95
96 buf[bytes_read] = '\0';
97
98 /*
99 * Files from sysfs sometimes contain a newline or other garbage that
100 * can confuse functions like strtoul() and ruin formatting in print
101 * statements. Replace the first non-printable character (space is
102 * considered printable) with a proper string terminator.
103 */
104 for (i = 0; i < len; i++) {
105 if (!isprint(buf[i])) {
106 buf[i] = '\0';
107 break;
108 }
109 }
110
111 close(fd);
112 return 0;
113}
114
115static int read_sysfs_int(const char *filename, unsigned long int *val)
116{
David Hendrickscebee892015-05-23 20:30:30 -0700117 char buf[32];
118 char *endptr;
119
120 if (read_sysfs_string(filename, buf, sizeof(buf)))
121 return 1;
122
123 errno = 0;
124 *val = strtoul(buf, &endptr, 0);
125 if (endptr != &buf[strlen(buf)]) {
126 msg_perr("Error reading %s\n", filename);
127 return 1;
128 }
129
130 if (errno) {
131 msg_perr("Error reading %s: %s\n", filename, strerror(errno));
132 return 1;
133 }
134
135 return 0;
136}
137
138/* returns 0 to indicate success, non-zero to indicate error */
139static int get_mtd_info(void)
140{
141 unsigned long int tmp;
142 char mtd_device_name[32];
143
144 /* Flags */
145 if (read_sysfs_int("flags", &tmp))
146 return 1;
147 if (tmp & MTD_WRITEABLE) {
148 /* cache for later use by write function */
149 mtd_device_is_writeable = 1;
150 }
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700151 if (tmp & MTD_NO_ERASE) {
152 mtd_no_erase = 1;
153 }
David Hendrickscebee892015-05-23 20:30:30 -0700154
155 /* Device name */
156 if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
157 return 1;
158
159 /* Total size */
160 if (read_sysfs_int("size", &mtd_total_size))
161 return 1;
162 if (__builtin_popcount(mtd_total_size) != 1) {
163 msg_perr("MTD size is not a power of 2\n");
164 return 1;
165 }
166
167 /* Erase size */
168 if (read_sysfs_int("erasesize", &mtd_erasesize))
169 return 1;
170 if (__builtin_popcount(mtd_erasesize) != 1) {
171 msg_perr("MTD erase size is not a power of 2\n");
172 return 1;
173 }
174
175 /* Erase regions */
176 if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
177 return 1;
178 if (mtd_numeraseregions != 0) {
179 msg_perr("Non-uniform eraseblock size is unsupported.\n");
180 return 1;
181 }
182
183 msg_pspew("%s: device_name: \"%s\", is_writeable: %d, "
184 "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
185 __func__, mtd_device_name, mtd_device_is_writeable,
186 mtd_numeraseregions, mtd_total_size, mtd_erasesize);
187
188 return 0;
189}
190
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700191static int linux_mtd_probe(struct flashctx *flash)
David Hendrickscebee892015-05-23 20:30:30 -0700192{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100193 flash->chip->wp = &wp_mtd;
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700194 if (mtd_no_erase)
195 flash->chip->feature_bits |= FEATURE_NO_ERASE;
Patrick Georgif3fa2992017-02-02 16:24:44 +0100196 flash->chip->tested = TEST_OK_PREW;
197 flash->chip->total_size = mtd_total_size / 1024; /* bytes -> kB */
198 flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
199 flash->chip->block_erasers[0].eraseblocks[0].count =
David Hendrickscebee892015-05-23 20:30:30 -0700200 mtd_total_size / mtd_erasesize;
201 return 1;
202}
203
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700204static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700205 unsigned int start, unsigned int len)
206{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100207 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
Brian Norris99c8ad22016-05-10 15:36:02 -0700208 unsigned int i;
209
David Hendrickscebee892015-05-23 20:30:30 -0700210 if (lseek(dev_fd, start, SEEK_SET) != start) {
211 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
212 return 1;
213 }
214
Brian Norris99c8ad22016-05-10 15:36:02 -0700215 for (i = 0; i < len; ) {
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700216 /*
217 * Try to align reads to eraseblock size.
218 * FIXME: Shouldn't actually be necessary, but not all MTD
219 * drivers handle arbitrary large reads well. See, for example,
220 * https://b/35573113
221 */
Brian Norris99c8ad22016-05-10 15:36:02 -0700222 unsigned int step = eb_size - ((start + i) % eb_size);
223 step = min(step, len - i);
224
225 if (read(dev_fd, buf + i, step) != step) {
226 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
227 step, start + i, strerror(errno));
228 return 1;
229 }
230
231 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700232 }
233
234 return 0;
235}
236
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700237/* this version assumes we must divide the write request into chunks ourselves */
Patrick Georgiab8353e2017-02-03 18:32:01 +0100238static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700239 unsigned int start, unsigned int len)
240{
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700241 unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
242 unsigned int i;
David Hendrickscebee892015-05-23 20:30:30 -0700243
244 if (!mtd_device_is_writeable)
245 return 1;
246
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700247 if (lseek(dev_fd, start, SEEK_SET) != start) {
248 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
249 return 1;
250 }
David Hendrickscebee892015-05-23 20:30:30 -0700251
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700252 /*
253 * Try to align writes to eraseblock size. We want these large enough
254 * to give MTD room for optimizing performance.
255 * FIXME: Shouldn't need to divide this up at all, but not all MTD
256 * drivers handle arbitrary large writes well. See, for example,
257 * https://b/35573113
258 */
259 for (i = 0; i < len; ) {
260 unsigned int step = chunksize - ((start + i) % chunksize);
261 step = min(step, len - i);
David Hendrickscebee892015-05-23 20:30:30 -0700262
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700263 if (write(dev_fd, buf + i, step) != step) {
264 msg_perr("Cannot write 0x%06x bytes at 0x%06x: %s\n",
265 step, start + i, strerror(errno));
266 return 1;
David Hendrickscebee892015-05-23 20:30:30 -0700267 }
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700268
269 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700270 }
271
272 return 0;
273}
274
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700275static int linux_mtd_erase(struct flashctx *flash,
David Hendrickscebee892015-05-23 20:30:30 -0700276 unsigned int start, unsigned int len)
277{
David Hendrickscebee892015-05-23 20:30:30 -0700278 uint32_t u;
279
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700280 if (mtd_no_erase) {
281 msg_perr("%s: device does not support erasing. Please file a "
282 "bug report at flashrom@flashrom.org\n", __func__);
283 return 1;
284 }
285
David Hendrickscebee892015-05-23 20:30:30 -0700286 if (mtd_numeraseregions != 0) {
287 /* TODO: Support non-uniform eraseblock size using
288 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
289 }
290
291 for (u = 0; u < len; u += mtd_erasesize) {
292 struct erase_info_user erase_info = {
293 .start = start + u,
294 .length = mtd_erasesize,
295 };
296
297 if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
298 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
299 return 1;
300 }
301 }
302
303 return 0;
304}
305
306static struct opaque_programmer programmer_linux_mtd = {
Brian Norris99c8ad22016-05-10 15:36:02 -0700307 /* max_data_{read,write} don't have any effect for this programmer */
David Hendrickscebee892015-05-23 20:30:30 -0700308 .max_data_read = MAX_DATA_UNSPECIFIED,
309 .max_data_write = MAX_DATA_UNSPECIFIED,
310 .probe = linux_mtd_probe,
311 .read = linux_mtd_read,
312 .write = linux_mtd_write,
313 .erase = linux_mtd_erase,
314};
315
316/* Returns 0 if setup is successful, non-zero to indicate error */
317static int linux_mtd_setup(int dev_num)
318{
319 char dev_path[16]; /* "/dev/mtdN" */
320 int ret = 1;
321
322 if (dev_num < 0) {
323 char *tmp, *p;
324
325 tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
326 if (!tmp) {
David Hendrickscf6fbe62015-09-24 14:22:39 -0700327 msg_pdbg("%s: NOR type device not found.\n", __func__);
David Hendrickscebee892015-05-23 20:30:30 -0700328 goto linux_mtd_setup_exit;
329 }
330
331 /* "tmp" should be something like "/sys/blah/mtdN/type" */
332 p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
333 while (p[0] == '/')
334 p++;
335
336 if (sscanf(p, "mtd%d", &dev_num) != 1) {
337 msg_perr("Can't obtain device number from \"%s\"\n", p);
338 free(tmp);
339 goto linux_mtd_setup_exit;
340 }
341 free(tmp);
342 }
343
344 snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
345 LINUX_MTD_SYSFS_ROOT, dev_num);
346 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
347 LINUX_DEV_ROOT, dev_num);
348 msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
349 __func__, sysfs_path, dev_path);
350
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -0700351 if (stat_mtd_files(dev_path))
David Hendrickscebee892015-05-23 20:30:30 -0700352 goto linux_mtd_setup_exit;
353
354 if (get_mtd_info())
355 goto linux_mtd_setup_exit;
356
357 if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
358 msg_pdbg("%s: failed to open %s: %s\n", __func__,
359 dev_path, strerror(errno));
360 goto linux_mtd_setup_exit;
361 }
362
363 ret = 0;
364linux_mtd_setup_exit:
365 return ret;
366}
367
David Hendricks93784b42016-08-09 17:00:38 -0700368static int linux_mtd_shutdown(void *data)
David Hendrickscebee892015-05-23 20:30:30 -0700369{
370 if (dev_fd != -1) {
371 close(dev_fd);
372 dev_fd = -1;
373 }
374
375 return 0;
376}
377
David Hendricksac1d25c2016-08-09 17:00:58 -0700378int linux_mtd_init(void)
David Hendrickscebee892015-05-23 20:30:30 -0700379{
380 char *param;
381 int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
382 int ret = 1;
383
384 if (alias && alias->type != ALIAS_HOST)
385 return 1;
386
387 param = extract_programmer_param("dev");
388 if (param) {
389 char *endptr;
390
391 dev_num = strtol(param, &endptr, 0);
392 if ((param == endptr) || (dev_num < 0)) {
393 msg_perr("Invalid device number %s. Use flashrom -p "
394 "linux_mtd:dev=N where N is a valid MTD "
395 "device number\n", param);
396 goto linux_mtd_init_exit;
397 }
398 }
399
400 if (linux_mtd_setup(dev_num))
401 goto linux_mtd_init_exit;
402
403 if (register_shutdown(linux_mtd_shutdown, NULL))
404 goto linux_mtd_init_exit;
405
406 register_opaque_programmer(&programmer_linux_mtd);
407
408 ret = 0;
409linux_mtd_init_exit:
410 msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
411 return ret;
412}
David Hendricks85f61c52015-06-04 19:52:59 -0700413
414/*
415 * Write-protect functions.
416 */
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700417static int mtd_wp_list_ranges(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700418{
419 /* TODO: implement this */
420 msg_perr("--wp-list is not currently implemented for MTD.\n");
421 return 1;
422}
423
424/*
425 * We only have MEMLOCK to enable write-protection for a particular block,
426 * so we need to do force the user to use --wp-range and --wp-enable
427 * command-line arguments simultaneously. (Fortunately, CrOS factory
428 * installer does this already).
429 *
430 * The --wp-range argument is processed first and will set these variables
431 * which --wp-enable will use afterward.
432 */
433static unsigned int wp_range_start;
434static unsigned int wp_range_len;
435static int wp_set_range_called = 0;
436
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700437static int mtd_wp_set_range(const struct flashctx *flash,
David Hendricks85f61c52015-06-04 19:52:59 -0700438 unsigned int start, unsigned int len)
439{
440 wp_range_start = start;
441 wp_range_len = len;
442
443 wp_set_range_called = 1;
444 return 0;
445}
446
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700447static int mtd_wp_enable_writeprotect(const struct flashctx *flash, enum wp_mode mode)
David Hendricks85f61c52015-06-04 19:52:59 -0700448{
449 struct erase_info_user entire_chip = {
450 .start = 0,
451 .length = mtd_total_size,
452 };
453 struct erase_info_user desired_range = {
454 .start = wp_range_start,
455 .length = wp_range_len,
456 };
457
458 if (!wp_set_range_called) {
459 msg_perr("For MTD, --wp-range and --wp-enable must be "
460 "used simultaneously.\n");
461 return 1;
462 }
463
464 /*
465 * MTD handles write-protection additively, so whatever new range is
466 * specified is added to the range which is currently protected. To be
467 * consistent with flashrom behavior with other programmer interfaces,
468 * we need to disable the current write protection and then enable
469 * it for the desired range.
470 */
471 if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
472 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
473 __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700474 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700475 return 1;
476 }
477
478 if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
479 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
480 __func__, strerror(errno));
481 return 1;
482 }
483
484 return 0;
485}
486
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700487static int mtd_wp_disable_writeprotect(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700488{
489 struct erase_info_user erase_info;
490
491 if (wp_set_range_called) {
492 erase_info.start = wp_range_start;
493 erase_info.length = wp_range_len;
494 } else {
495 erase_info.start = 0;
496 erase_info.length = mtd_total_size;
497 }
498
499 if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
500 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700501 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700502 return 1;
503 }
504
505 return 0;
506}
507
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700508static int mtd_wp_status(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700509{
Brian Norris3ce4c472016-06-02 16:40:14 -0700510 uint32_t start = 0, len = 0;
511 int start_found = 0;
David Hendricks85f61c52015-06-04 19:52:59 -0700512 unsigned int u;
513
514 /* For now, assume only one contiguous region can be locked (NOR) */
515 /* FIXME: use flash struct members instead of raw MTD values here */
Wei-Ning Huang699269b2016-03-02 22:08:05 +0800516 for (u = 0; u < mtd_total_size; u += mtd_erasesize) {
David Hendricks85f61c52015-06-04 19:52:59 -0700517 int rc;
518 struct erase_info_user erase_info = {
519 .start = u,
520 .length = mtd_erasesize,
521 };
522
523 rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
524 if (rc < 0) {
525 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
526 return 1;
527 } else if (rc == 1) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800528 if (!start_found) {
David Hendricks85f61c52015-06-04 19:52:59 -0700529 start = erase_info.start;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800530 start_found = 1;
531 }
Brian Norris3ce4c472016-06-02 16:40:14 -0700532 len += mtd_erasesize;
David Hendricks85f61c52015-06-04 19:52:59 -0700533 } else if (rc == 0) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800534 if (start_found) {
Brian Norris3ce4c472016-06-02 16:40:14 -0700535 /* TODO: changes required for supporting non-contiguous locked regions */
536 break;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800537 }
David Hendricks85f61c52015-06-04 19:52:59 -0700538 }
539
David Hendricks85f61c52015-06-04 19:52:59 -0700540 }
541
Wei-Ning Huangca907072016-04-27 11:30:17 +0800542 msg_cinfo("WP: write protect is %s.\n",
Brian Norris3ce4c472016-06-02 16:40:14 -0700543 start_found ? "enabled": "disabled");
Wei-Ning Huangca907072016-04-27 11:30:17 +0800544 msg_pinfo("WP: write protect range: start=0x%08x, "
Brian Norris3ce4c472016-06-02 16:40:14 -0700545 "len=0x%08x\n", start, len);
Wei-Ning Huangca907072016-04-27 11:30:17 +0800546
David Hendricks85f61c52015-06-04 19:52:59 -0700547 return 0;
548}
549
550static struct wp wp_mtd = {
551 .list_ranges = mtd_wp_list_ranges,
552 .set_range = mtd_wp_set_range,
553 .enable = mtd_wp_enable_writeprotect,
554 .disable = mtd_wp_disable_writeprotect,
555 .wp_status = mtd_wp_status,
556};