blob: 188b52940ba896d0be5db734405487670e2309a9 [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 *
David Hendrickscebee892015-05-23 20:30:30 -070015 */
16
17#include <ctype.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <inttypes.h>
21#include <libgen.h>
Gwendal Grignoudb133582018-03-23 12:28:20 -070022#include <limits.h>
David Hendrickscebee892015-05-23 20:30:30 -070023#include <stdio.h>
24#include <stdlib.h>
25#include <mtd/mtd-user.h>
26#include <string.h>
27#include <sys/ioctl.h>
28#include <sys/stat.h>
29#include <unistd.h>
30
31#include "file.h"
32#include "flash.h"
33#include "programmer.h"
David Hendricks85f61c52015-06-04 19:52:59 -070034#include "writeprotect.h"
David Hendrickscebee892015-05-23 20:30:30 -070035
36#define LINUX_DEV_ROOT "/dev"
37#define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd"
38
39/* enough space for LINUX_MTD_SYSFS_ROOT + directory name + filename */
40static char sysfs_path[PATH_MAX];
41
42static int dev_fd = -1;
43
David Hendrickscebee892015-05-23 20:30:30 -070044static int mtd_device_is_writeable;
45
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -070046static int mtd_no_erase;
47
David Hendrickscebee892015-05-23 20:30:30 -070048/* Size info is presented in bytes in sysfs. */
49static unsigned long int mtd_total_size;
50static unsigned long int mtd_numeraseregions;
51static unsigned long int mtd_erasesize; /* only valid if numeraseregions is 0 */
52
David Hendricks85f61c52015-06-04 19:52:59 -070053static struct wp wp_mtd; /* forward declaration */
54
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -070055static int stat_mtd_files(char *dev_path)
David Hendrickscebee892015-05-23 20:30:30 -070056{
57 struct stat s;
58
59 errno = 0;
60 if (stat(dev_path, &s) < 0) {
61 msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
62 return 1;
63 }
64
65 if (lstat(sysfs_path, &s) < 0) {
66 msg_pdbg("Cannot stat \"%s\" : %s\n",
67 sysfs_path, strerror(errno));
68 return 1;
69 }
70
71 return 0;
72}
73
74/* read a string from a sysfs file and sanitize it */
75static int read_sysfs_string(const char *filename, char *buf, int len)
76{
77 int fd, bytes_read, i;
78 char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
79
80 snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
81
82 if ((fd = open(path, O_RDONLY)) < 0) {
83 msg_perr("Cannot open %s\n", path);
84 return 1;
85 }
86
87 if ((bytes_read = read(fd, buf, len - 1)) < 0) {
88 msg_perr("Cannot read %s\n", path);
89 close(fd);
90 return 1;
91 }
92
93 buf[bytes_read] = '\0';
94
95 /*
96 * Files from sysfs sometimes contain a newline or other garbage that
97 * can confuse functions like strtoul() and ruin formatting in print
98 * statements. Replace the first non-printable character (space is
99 * considered printable) with a proper string terminator.
100 */
101 for (i = 0; i < len; i++) {
102 if (!isprint(buf[i])) {
103 buf[i] = '\0';
104 break;
105 }
106 }
107
108 close(fd);
109 return 0;
110}
111
112static int read_sysfs_int(const char *filename, unsigned long int *val)
113{
David Hendrickscebee892015-05-23 20:30:30 -0700114 char buf[32];
115 char *endptr;
116
117 if (read_sysfs_string(filename, buf, sizeof(buf)))
118 return 1;
119
120 errno = 0;
121 *val = strtoul(buf, &endptr, 0);
122 if (endptr != &buf[strlen(buf)]) {
123 msg_perr("Error reading %s\n", filename);
124 return 1;
125 }
126
127 if (errno) {
128 msg_perr("Error reading %s: %s\n", filename, strerror(errno));
129 return 1;
130 }
131
132 return 0;
133}
134
135/* returns 0 to indicate success, non-zero to indicate error */
136static int get_mtd_info(void)
137{
138 unsigned long int tmp;
139 char mtd_device_name[32];
140
141 /* Flags */
142 if (read_sysfs_int("flags", &tmp))
143 return 1;
144 if (tmp & MTD_WRITEABLE) {
145 /* cache for later use by write function */
146 mtd_device_is_writeable = 1;
147 }
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700148 if (tmp & MTD_NO_ERASE) {
149 mtd_no_erase = 1;
150 }
David Hendrickscebee892015-05-23 20:30:30 -0700151
152 /* Device name */
153 if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
154 return 1;
155
156 /* Total size */
157 if (read_sysfs_int("size", &mtd_total_size))
158 return 1;
159 if (__builtin_popcount(mtd_total_size) != 1) {
160 msg_perr("MTD size is not a power of 2\n");
161 return 1;
162 }
163
164 /* Erase size */
165 if (read_sysfs_int("erasesize", &mtd_erasesize))
166 return 1;
167 if (__builtin_popcount(mtd_erasesize) != 1) {
168 msg_perr("MTD erase size is not a power of 2\n");
169 return 1;
170 }
171
172 /* Erase regions */
173 if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
174 return 1;
175 if (mtd_numeraseregions != 0) {
176 msg_perr("Non-uniform eraseblock size is unsupported.\n");
177 return 1;
178 }
179
180 msg_pspew("%s: device_name: \"%s\", is_writeable: %d, "
181 "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
182 __func__, mtd_device_name, mtd_device_is_writeable,
183 mtd_numeraseregions, mtd_total_size, mtd_erasesize);
184
185 return 0;
186}
187
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700188static int linux_mtd_probe(struct flashctx *flash)
David Hendrickscebee892015-05-23 20:30:30 -0700189{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100190 flash->chip->wp = &wp_mtd;
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700191 if (mtd_no_erase)
192 flash->chip->feature_bits |= FEATURE_NO_ERASE;
Patrick Georgif3fa2992017-02-02 16:24:44 +0100193 flash->chip->tested = TEST_OK_PREW;
194 flash->chip->total_size = mtd_total_size / 1024; /* bytes -> kB */
195 flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
196 flash->chip->block_erasers[0].eraseblocks[0].count =
David Hendrickscebee892015-05-23 20:30:30 -0700197 mtd_total_size / mtd_erasesize;
198 return 1;
199}
200
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700201static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700202 unsigned int start, unsigned int len)
203{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100204 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
Brian Norris99c8ad22016-05-10 15:36:02 -0700205 unsigned int i;
206
David Hendrickscebee892015-05-23 20:30:30 -0700207 if (lseek(dev_fd, start, SEEK_SET) != start) {
208 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
209 return 1;
210 }
211
Brian Norris99c8ad22016-05-10 15:36:02 -0700212 for (i = 0; i < len; ) {
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700213 /*
214 * Try to align reads to eraseblock size.
215 * FIXME: Shouldn't actually be necessary, but not all MTD
216 * drivers handle arbitrary large reads well. See, for example,
217 * https://b/35573113
218 */
Brian Norris99c8ad22016-05-10 15:36:02 -0700219 unsigned int step = eb_size - ((start + i) % eb_size);
220 step = min(step, len - i);
221
222 if (read(dev_fd, buf + i, step) != step) {
223 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
224 step, start + i, strerror(errno));
225 return 1;
226 }
227
228 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700229 }
230
231 return 0;
232}
233
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700234/* this version assumes we must divide the write request into chunks ourselves */
Patrick Georgiab8353e2017-02-03 18:32:01 +0100235static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700236 unsigned int start, unsigned int len)
237{
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700238 unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
239 unsigned int i;
David Hendrickscebee892015-05-23 20:30:30 -0700240
241 if (!mtd_device_is_writeable)
242 return 1;
243
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700244 if (lseek(dev_fd, start, SEEK_SET) != start) {
245 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
246 return 1;
247 }
David Hendrickscebee892015-05-23 20:30:30 -0700248
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700249 /*
250 * Try to align writes to eraseblock size. We want these large enough
251 * to give MTD room for optimizing performance.
252 * FIXME: Shouldn't need to divide this up at all, but not all MTD
253 * drivers handle arbitrary large writes well. See, for example,
254 * https://b/35573113
255 */
256 for (i = 0; i < len; ) {
257 unsigned int step = chunksize - ((start + i) % chunksize);
258 step = min(step, len - i);
David Hendrickscebee892015-05-23 20:30:30 -0700259
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700260 if (write(dev_fd, buf + i, step) != step) {
261 msg_perr("Cannot write 0x%06x bytes at 0x%06x: %s\n",
262 step, start + i, strerror(errno));
263 return 1;
David Hendrickscebee892015-05-23 20:30:30 -0700264 }
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700265
266 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700267 }
268
269 return 0;
270}
271
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700272static int linux_mtd_erase(struct flashctx *flash,
David Hendrickscebee892015-05-23 20:30:30 -0700273 unsigned int start, unsigned int len)
274{
David Hendrickscebee892015-05-23 20:30:30 -0700275 uint32_t u;
276
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700277 if (mtd_no_erase) {
278 msg_perr("%s: device does not support erasing. Please file a "
279 "bug report at flashrom@flashrom.org\n", __func__);
280 return 1;
281 }
282
David Hendrickscebee892015-05-23 20:30:30 -0700283 if (mtd_numeraseregions != 0) {
284 /* TODO: Support non-uniform eraseblock size using
285 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
286 }
287
288 for (u = 0; u < len; u += mtd_erasesize) {
289 struct erase_info_user erase_info = {
290 .start = start + u,
291 .length = mtd_erasesize,
292 };
293
294 if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
295 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
296 return 1;
297 }
298 }
299
300 return 0;
301}
302
303static struct opaque_programmer programmer_linux_mtd = {
Brian Norris99c8ad22016-05-10 15:36:02 -0700304 /* max_data_{read,write} don't have any effect for this programmer */
David Hendrickscebee892015-05-23 20:30:30 -0700305 .max_data_read = MAX_DATA_UNSPECIFIED,
306 .max_data_write = MAX_DATA_UNSPECIFIED,
307 .probe = linux_mtd_probe,
308 .read = linux_mtd_read,
309 .write = linux_mtd_write,
310 .erase = linux_mtd_erase,
311};
312
313/* Returns 0 if setup is successful, non-zero to indicate error */
314static int linux_mtd_setup(int dev_num)
315{
316 char dev_path[16]; /* "/dev/mtdN" */
317 int ret = 1;
318
319 if (dev_num < 0) {
320 char *tmp, *p;
321
322 tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
323 if (!tmp) {
David Hendrickscf6fbe62015-09-24 14:22:39 -0700324 msg_pdbg("%s: NOR type device not found.\n", __func__);
David Hendrickscebee892015-05-23 20:30:30 -0700325 goto linux_mtd_setup_exit;
326 }
327
328 /* "tmp" should be something like "/sys/blah/mtdN/type" */
329 p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
330 while (p[0] == '/')
331 p++;
332
333 if (sscanf(p, "mtd%d", &dev_num) != 1) {
334 msg_perr("Can't obtain device number from \"%s\"\n", p);
335 free(tmp);
336 goto linux_mtd_setup_exit;
337 }
338 free(tmp);
339 }
340
341 snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
342 LINUX_MTD_SYSFS_ROOT, dev_num);
343 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
344 LINUX_DEV_ROOT, dev_num);
345 msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
346 __func__, sysfs_path, dev_path);
347
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -0700348 if (stat_mtd_files(dev_path))
David Hendrickscebee892015-05-23 20:30:30 -0700349 goto linux_mtd_setup_exit;
350
351 if (get_mtd_info())
352 goto linux_mtd_setup_exit;
353
354 if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
355 msg_pdbg("%s: failed to open %s: %s\n", __func__,
356 dev_path, strerror(errno));
357 goto linux_mtd_setup_exit;
358 }
359
360 ret = 0;
361linux_mtd_setup_exit:
362 return ret;
363}
364
David Hendricks93784b42016-08-09 17:00:38 -0700365static int linux_mtd_shutdown(void *data)
David Hendrickscebee892015-05-23 20:30:30 -0700366{
367 if (dev_fd != -1) {
368 close(dev_fd);
369 dev_fd = -1;
370 }
371
372 return 0;
373}
374
David Hendricksac1d25c2016-08-09 17:00:58 -0700375int linux_mtd_init(void)
David Hendrickscebee892015-05-23 20:30:30 -0700376{
377 char *param;
378 int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
379 int ret = 1;
380
381 if (alias && alias->type != ALIAS_HOST)
382 return 1;
383
384 param = extract_programmer_param("dev");
385 if (param) {
386 char *endptr;
387
388 dev_num = strtol(param, &endptr, 0);
389 if ((param == endptr) || (dev_num < 0)) {
390 msg_perr("Invalid device number %s. Use flashrom -p "
391 "linux_mtd:dev=N where N is a valid MTD "
392 "device number\n", param);
393 goto linux_mtd_init_exit;
394 }
395 }
396
397 if (linux_mtd_setup(dev_num))
398 goto linux_mtd_init_exit;
399
400 if (register_shutdown(linux_mtd_shutdown, NULL))
401 goto linux_mtd_init_exit;
402
403 register_opaque_programmer(&programmer_linux_mtd);
404
405 ret = 0;
406linux_mtd_init_exit:
407 msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
408 return ret;
409}
David Hendricks85f61c52015-06-04 19:52:59 -0700410
411/*
412 * Write-protect functions.
413 */
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700414static int mtd_wp_list_ranges(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700415{
416 /* TODO: implement this */
417 msg_perr("--wp-list is not currently implemented for MTD.\n");
418 return 1;
419}
420
421/*
422 * We only have MEMLOCK to enable write-protection for a particular block,
423 * so we need to do force the user to use --wp-range and --wp-enable
424 * command-line arguments simultaneously. (Fortunately, CrOS factory
425 * installer does this already).
426 *
427 * The --wp-range argument is processed first and will set these variables
428 * which --wp-enable will use afterward.
429 */
430static unsigned int wp_range_start;
431static unsigned int wp_range_len;
432static int wp_set_range_called = 0;
433
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700434static int mtd_wp_set_range(const struct flashctx *flash,
David Hendricks85f61c52015-06-04 19:52:59 -0700435 unsigned int start, unsigned int len)
436{
437 wp_range_start = start;
438 wp_range_len = len;
439
440 wp_set_range_called = 1;
441 return 0;
442}
443
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700444static int mtd_wp_enable_writeprotect(const struct flashctx *flash, enum wp_mode mode)
David Hendricks85f61c52015-06-04 19:52:59 -0700445{
446 struct erase_info_user entire_chip = {
447 .start = 0,
448 .length = mtd_total_size,
449 };
450 struct erase_info_user desired_range = {
451 .start = wp_range_start,
452 .length = wp_range_len,
453 };
454
455 if (!wp_set_range_called) {
456 msg_perr("For MTD, --wp-range and --wp-enable must be "
457 "used simultaneously.\n");
458 return 1;
459 }
460
461 /*
462 * MTD handles write-protection additively, so whatever new range is
463 * specified is added to the range which is currently protected. To be
464 * consistent with flashrom behavior with other programmer interfaces,
465 * we need to disable the current write protection and then enable
466 * it for the desired range.
467 */
468 if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
469 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
470 __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700471 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700472 return 1;
473 }
474
475 if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
476 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
477 __func__, strerror(errno));
478 return 1;
479 }
480
481 return 0;
482}
483
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700484static int mtd_wp_disable_writeprotect(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700485{
486 struct erase_info_user erase_info;
487
488 if (wp_set_range_called) {
489 erase_info.start = wp_range_start;
490 erase_info.length = wp_range_len;
491 } else {
492 erase_info.start = 0;
493 erase_info.length = mtd_total_size;
494 }
495
496 if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
497 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700498 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700499 return 1;
500 }
501
502 return 0;
503}
504
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700505static int mtd_wp_status(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700506{
Brian Norris3ce4c472016-06-02 16:40:14 -0700507 uint32_t start = 0, len = 0;
508 int start_found = 0;
David Hendricks85f61c52015-06-04 19:52:59 -0700509 unsigned int u;
510
511 /* For now, assume only one contiguous region can be locked (NOR) */
512 /* FIXME: use flash struct members instead of raw MTD values here */
Wei-Ning Huang699269b2016-03-02 22:08:05 +0800513 for (u = 0; u < mtd_total_size; u += mtd_erasesize) {
David Hendricks85f61c52015-06-04 19:52:59 -0700514 int rc;
515 struct erase_info_user erase_info = {
516 .start = u,
517 .length = mtd_erasesize,
518 };
519
520 rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
521 if (rc < 0) {
522 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
523 return 1;
524 } else if (rc == 1) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800525 if (!start_found) {
David Hendricks85f61c52015-06-04 19:52:59 -0700526 start = erase_info.start;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800527 start_found = 1;
528 }
Brian Norris3ce4c472016-06-02 16:40:14 -0700529 len += mtd_erasesize;
David Hendricks85f61c52015-06-04 19:52:59 -0700530 } else if (rc == 0) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800531 if (start_found) {
Brian Norris3ce4c472016-06-02 16:40:14 -0700532 /* TODO: changes required for supporting non-contiguous locked regions */
533 break;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800534 }
David Hendricks85f61c52015-06-04 19:52:59 -0700535 }
536
David Hendricks85f61c52015-06-04 19:52:59 -0700537 }
538
Wei-Ning Huangca907072016-04-27 11:30:17 +0800539 msg_cinfo("WP: write protect is %s.\n",
Brian Norris3ce4c472016-06-02 16:40:14 -0700540 start_found ? "enabled": "disabled");
Wei-Ning Huangca907072016-04-27 11:30:17 +0800541 msg_pinfo("WP: write protect range: start=0x%08x, "
Brian Norris3ce4c472016-06-02 16:40:14 -0700542 "len=0x%08x\n", start, len);
Wei-Ning Huangca907072016-04-27 11:30:17 +0800543
David Hendricks85f61c52015-06-04 19:52:59 -0700544 return 0;
545}
546
547static struct wp wp_mtd = {
548 .list_ranges = mtd_wp_list_ranges,
549 .set_range = mtd_wp_set_range,
550 .enable = mtd_wp_enable_writeprotect,
551 .disable = mtd_wp_disable_writeprotect,
552 .wp_status = mtd_wp_status,
553};