blob: 9cb5c6883ab65f2a1af5730b98bad96d99c0709a [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>
25#include <stdio.h>
26#include <stdlib.h>
27#include <mtd/mtd-user.h>
28#include <string.h>
29#include <sys/ioctl.h>
30#include <sys/stat.h>
31#include <unistd.h>
32
33#include "file.h"
34#include "flash.h"
35#include "programmer.h"
David Hendricks85f61c52015-06-04 19:52:59 -070036#include "writeprotect.h"
David Hendrickscebee892015-05-23 20:30:30 -070037
38#define LINUX_DEV_ROOT "/dev"
39#define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd"
40
41/* enough space for LINUX_MTD_SYSFS_ROOT + directory name + filename */
42static char sysfs_path[PATH_MAX];
43
44static int dev_fd = -1;
45
David Hendrickscebee892015-05-23 20:30:30 -070046static int mtd_device_is_writeable;
47
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -070048static int mtd_no_erase;
49
David Hendrickscebee892015-05-23 20:30:30 -070050/* Size info is presented in bytes in sysfs. */
51static unsigned long int mtd_total_size;
52static unsigned long int mtd_numeraseregions;
53static unsigned long int mtd_erasesize; /* only valid if numeraseregions is 0 */
54
David Hendricks85f61c52015-06-04 19:52:59 -070055static struct wp wp_mtd; /* forward declaration */
56
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -070057static int stat_mtd_files(char *dev_path)
David Hendrickscebee892015-05-23 20:30:30 -070058{
59 struct stat s;
60
61 errno = 0;
62 if (stat(dev_path, &s) < 0) {
63 msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
64 return 1;
65 }
66
67 if (lstat(sysfs_path, &s) < 0) {
68 msg_pdbg("Cannot stat \"%s\" : %s\n",
69 sysfs_path, strerror(errno));
70 return 1;
71 }
72
73 return 0;
74}
75
76/* read a string from a sysfs file and sanitize it */
77static int read_sysfs_string(const char *filename, char *buf, int len)
78{
79 int fd, bytes_read, i;
80 char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
81
82 snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
83
84 if ((fd = open(path, O_RDONLY)) < 0) {
85 msg_perr("Cannot open %s\n", path);
86 return 1;
87 }
88
89 if ((bytes_read = read(fd, buf, len - 1)) < 0) {
90 msg_perr("Cannot read %s\n", path);
91 close(fd);
92 return 1;
93 }
94
95 buf[bytes_read] = '\0';
96
97 /*
98 * Files from sysfs sometimes contain a newline or other garbage that
99 * can confuse functions like strtoul() and ruin formatting in print
100 * statements. Replace the first non-printable character (space is
101 * considered printable) with a proper string terminator.
102 */
103 for (i = 0; i < len; i++) {
104 if (!isprint(buf[i])) {
105 buf[i] = '\0';
106 break;
107 }
108 }
109
110 close(fd);
111 return 0;
112}
113
114static int read_sysfs_int(const char *filename, unsigned long int *val)
115{
David Hendrickscebee892015-05-23 20:30:30 -0700116 char buf[32];
117 char *endptr;
118
119 if (read_sysfs_string(filename, buf, sizeof(buf)))
120 return 1;
121
122 errno = 0;
123 *val = strtoul(buf, &endptr, 0);
124 if (endptr != &buf[strlen(buf)]) {
125 msg_perr("Error reading %s\n", filename);
126 return 1;
127 }
128
129 if (errno) {
130 msg_perr("Error reading %s: %s\n", filename, strerror(errno));
131 return 1;
132 }
133
134 return 0;
135}
136
137/* returns 0 to indicate success, non-zero to indicate error */
138static int get_mtd_info(void)
139{
140 unsigned long int tmp;
141 char mtd_device_name[32];
142
143 /* Flags */
144 if (read_sysfs_int("flags", &tmp))
145 return 1;
146 if (tmp & MTD_WRITEABLE) {
147 /* cache for later use by write function */
148 mtd_device_is_writeable = 1;
149 }
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700150 if (tmp & MTD_NO_ERASE) {
151 mtd_no_erase = 1;
152 }
David Hendrickscebee892015-05-23 20:30:30 -0700153
154 /* Device name */
155 if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
156 return 1;
157
158 /* Total size */
159 if (read_sysfs_int("size", &mtd_total_size))
160 return 1;
161 if (__builtin_popcount(mtd_total_size) != 1) {
162 msg_perr("MTD size is not a power of 2\n");
163 return 1;
164 }
165
166 /* Erase size */
167 if (read_sysfs_int("erasesize", &mtd_erasesize))
168 return 1;
169 if (__builtin_popcount(mtd_erasesize) != 1) {
170 msg_perr("MTD erase size is not a power of 2\n");
171 return 1;
172 }
173
174 /* Erase regions */
175 if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
176 return 1;
177 if (mtd_numeraseregions != 0) {
178 msg_perr("Non-uniform eraseblock size is unsupported.\n");
179 return 1;
180 }
181
182 msg_pspew("%s: device_name: \"%s\", is_writeable: %d, "
183 "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
184 __func__, mtd_device_name, mtd_device_is_writeable,
185 mtd_numeraseregions, mtd_total_size, mtd_erasesize);
186
187 return 0;
188}
189
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700190static int linux_mtd_probe(struct flashctx *flash)
David Hendrickscebee892015-05-23 20:30:30 -0700191{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100192 flash->chip->wp = &wp_mtd;
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700193 if (mtd_no_erase)
194 flash->chip->feature_bits |= FEATURE_NO_ERASE;
Patrick Georgif3fa2992017-02-02 16:24:44 +0100195 flash->chip->tested = TEST_OK_PREW;
196 flash->chip->total_size = mtd_total_size / 1024; /* bytes -> kB */
197 flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
198 flash->chip->block_erasers[0].eraseblocks[0].count =
David Hendrickscebee892015-05-23 20:30:30 -0700199 mtd_total_size / mtd_erasesize;
200 return 1;
201}
202
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700203static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700204 unsigned int start, unsigned int len)
205{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100206 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
Brian Norris99c8ad22016-05-10 15:36:02 -0700207 unsigned int i;
208
David Hendrickscebee892015-05-23 20:30:30 -0700209 if (lseek(dev_fd, start, SEEK_SET) != start) {
210 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
211 return 1;
212 }
213
Brian Norris99c8ad22016-05-10 15:36:02 -0700214 for (i = 0; i < len; ) {
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700215 /*
216 * Try to align reads to eraseblock size.
217 * FIXME: Shouldn't actually be necessary, but not all MTD
218 * drivers handle arbitrary large reads well. See, for example,
219 * https://b/35573113
220 */
Brian Norris99c8ad22016-05-10 15:36:02 -0700221 unsigned int step = eb_size - ((start + i) % eb_size);
222 step = min(step, len - i);
223
224 if (read(dev_fd, buf + i, step) != step) {
225 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
226 step, start + i, strerror(errno));
227 return 1;
228 }
229
230 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700231 }
232
233 return 0;
234}
235
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700236/* this version assumes we must divide the write request into chunks ourselves */
Patrick Georgiab8353e2017-02-03 18:32:01 +0100237static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700238 unsigned int start, unsigned int len)
239{
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700240 unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
241 unsigned int i;
David Hendrickscebee892015-05-23 20:30:30 -0700242
243 if (!mtd_device_is_writeable)
244 return 1;
245
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700246 if (lseek(dev_fd, start, SEEK_SET) != start) {
247 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
248 return 1;
249 }
David Hendrickscebee892015-05-23 20:30:30 -0700250
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700251 /*
252 * Try to align writes to eraseblock size. We want these large enough
253 * to give MTD room for optimizing performance.
254 * FIXME: Shouldn't need to divide this up at all, but not all MTD
255 * drivers handle arbitrary large writes well. See, for example,
256 * https://b/35573113
257 */
258 for (i = 0; i < len; ) {
259 unsigned int step = chunksize - ((start + i) % chunksize);
260 step = min(step, len - i);
David Hendrickscebee892015-05-23 20:30:30 -0700261
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700262 if (write(dev_fd, buf + i, step) != step) {
263 msg_perr("Cannot write 0x%06x bytes at 0x%06x: %s\n",
264 step, start + i, strerror(errno));
265 return 1;
David Hendrickscebee892015-05-23 20:30:30 -0700266 }
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700267
268 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700269 }
270
271 return 0;
272}
273
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700274static int linux_mtd_erase(struct flashctx *flash,
David Hendrickscebee892015-05-23 20:30:30 -0700275 unsigned int start, unsigned int len)
276{
David Hendrickscebee892015-05-23 20:30:30 -0700277 uint32_t u;
278
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700279 if (mtd_no_erase) {
280 msg_perr("%s: device does not support erasing. Please file a "
281 "bug report at flashrom@flashrom.org\n", __func__);
282 return 1;
283 }
284
David Hendrickscebee892015-05-23 20:30:30 -0700285 if (mtd_numeraseregions != 0) {
286 /* TODO: Support non-uniform eraseblock size using
287 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
288 }
289
290 for (u = 0; u < len; u += mtd_erasesize) {
291 struct erase_info_user erase_info = {
292 .start = start + u,
293 .length = mtd_erasesize,
294 };
295
296 if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
297 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
298 return 1;
299 }
300 }
301
302 return 0;
303}
304
305static struct opaque_programmer programmer_linux_mtd = {
Brian Norris99c8ad22016-05-10 15:36:02 -0700306 /* max_data_{read,write} don't have any effect for this programmer */
David Hendrickscebee892015-05-23 20:30:30 -0700307 .max_data_read = MAX_DATA_UNSPECIFIED,
308 .max_data_write = MAX_DATA_UNSPECIFIED,
309 .probe = linux_mtd_probe,
310 .read = linux_mtd_read,
311 .write = linux_mtd_write,
312 .erase = linux_mtd_erase,
313};
314
315/* Returns 0 if setup is successful, non-zero to indicate error */
316static int linux_mtd_setup(int dev_num)
317{
318 char dev_path[16]; /* "/dev/mtdN" */
319 int ret = 1;
320
321 if (dev_num < 0) {
322 char *tmp, *p;
323
324 tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
325 if (!tmp) {
David Hendrickscf6fbe62015-09-24 14:22:39 -0700326 msg_pdbg("%s: NOR type device not found.\n", __func__);
David Hendrickscebee892015-05-23 20:30:30 -0700327 goto linux_mtd_setup_exit;
328 }
329
330 /* "tmp" should be something like "/sys/blah/mtdN/type" */
331 p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
332 while (p[0] == '/')
333 p++;
334
335 if (sscanf(p, "mtd%d", &dev_num) != 1) {
336 msg_perr("Can't obtain device number from \"%s\"\n", p);
337 free(tmp);
338 goto linux_mtd_setup_exit;
339 }
340 free(tmp);
341 }
342
343 snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
344 LINUX_MTD_SYSFS_ROOT, dev_num);
345 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
346 LINUX_DEV_ROOT, dev_num);
347 msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
348 __func__, sysfs_path, dev_path);
349
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -0700350 if (stat_mtd_files(dev_path))
David Hendrickscebee892015-05-23 20:30:30 -0700351 goto linux_mtd_setup_exit;
352
353 if (get_mtd_info())
354 goto linux_mtd_setup_exit;
355
356 if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
357 msg_pdbg("%s: failed to open %s: %s\n", __func__,
358 dev_path, strerror(errno));
359 goto linux_mtd_setup_exit;
360 }
361
362 ret = 0;
363linux_mtd_setup_exit:
364 return ret;
365}
366
David Hendricks93784b42016-08-09 17:00:38 -0700367static int linux_mtd_shutdown(void *data)
David Hendrickscebee892015-05-23 20:30:30 -0700368{
369 if (dev_fd != -1) {
370 close(dev_fd);
371 dev_fd = -1;
372 }
373
374 return 0;
375}
376
David Hendricksac1d25c2016-08-09 17:00:58 -0700377int linux_mtd_init(void)
David Hendrickscebee892015-05-23 20:30:30 -0700378{
379 char *param;
380 int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
381 int ret = 1;
382
383 if (alias && alias->type != ALIAS_HOST)
384 return 1;
385
386 param = extract_programmer_param("dev");
387 if (param) {
388 char *endptr;
389
390 dev_num = strtol(param, &endptr, 0);
391 if ((param == endptr) || (dev_num < 0)) {
392 msg_perr("Invalid device number %s. Use flashrom -p "
393 "linux_mtd:dev=N where N is a valid MTD "
394 "device number\n", param);
395 goto linux_mtd_init_exit;
396 }
397 }
398
399 if (linux_mtd_setup(dev_num))
400 goto linux_mtd_init_exit;
401
402 if (register_shutdown(linux_mtd_shutdown, NULL))
403 goto linux_mtd_init_exit;
404
405 register_opaque_programmer(&programmer_linux_mtd);
406
407 ret = 0;
408linux_mtd_init_exit:
409 msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
410 return ret;
411}
David Hendricks85f61c52015-06-04 19:52:59 -0700412
413/*
414 * Write-protect functions.
415 */
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700416static int mtd_wp_list_ranges(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700417{
418 /* TODO: implement this */
419 msg_perr("--wp-list is not currently implemented for MTD.\n");
420 return 1;
421}
422
423/*
424 * We only have MEMLOCK to enable write-protection for a particular block,
425 * so we need to do force the user to use --wp-range and --wp-enable
426 * command-line arguments simultaneously. (Fortunately, CrOS factory
427 * installer does this already).
428 *
429 * The --wp-range argument is processed first and will set these variables
430 * which --wp-enable will use afterward.
431 */
432static unsigned int wp_range_start;
433static unsigned int wp_range_len;
434static int wp_set_range_called = 0;
435
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700436static int mtd_wp_set_range(const struct flashctx *flash,
David Hendricks85f61c52015-06-04 19:52:59 -0700437 unsigned int start, unsigned int len)
438{
439 wp_range_start = start;
440 wp_range_len = len;
441
442 wp_set_range_called = 1;
443 return 0;
444}
445
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700446static int mtd_wp_enable_writeprotect(const struct flashctx *flash, enum wp_mode mode)
David Hendricks85f61c52015-06-04 19:52:59 -0700447{
448 struct erase_info_user entire_chip = {
449 .start = 0,
450 .length = mtd_total_size,
451 };
452 struct erase_info_user desired_range = {
453 .start = wp_range_start,
454 .length = wp_range_len,
455 };
456
457 if (!wp_set_range_called) {
458 msg_perr("For MTD, --wp-range and --wp-enable must be "
459 "used simultaneously.\n");
460 return 1;
461 }
462
463 /*
464 * MTD handles write-protection additively, so whatever new range is
465 * specified is added to the range which is currently protected. To be
466 * consistent with flashrom behavior with other programmer interfaces,
467 * we need to disable the current write protection and then enable
468 * it for the desired range.
469 */
470 if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
471 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
472 __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700473 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700474 return 1;
475 }
476
477 if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
478 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
479 __func__, strerror(errno));
480 return 1;
481 }
482
483 return 0;
484}
485
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700486static int mtd_wp_disable_writeprotect(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700487{
488 struct erase_info_user erase_info;
489
490 if (wp_set_range_called) {
491 erase_info.start = wp_range_start;
492 erase_info.length = wp_range_len;
493 } else {
494 erase_info.start = 0;
495 erase_info.length = mtd_total_size;
496 }
497
498 if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
499 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700500 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700501 return 1;
502 }
503
504 return 0;
505}
506
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700507static int mtd_wp_status(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700508{
Brian Norris3ce4c472016-06-02 16:40:14 -0700509 uint32_t start = 0, len = 0;
510 int start_found = 0;
David Hendricks85f61c52015-06-04 19:52:59 -0700511 unsigned int u;
512
513 /* For now, assume only one contiguous region can be locked (NOR) */
514 /* FIXME: use flash struct members instead of raw MTD values here */
Wei-Ning Huang699269b2016-03-02 22:08:05 +0800515 for (u = 0; u < mtd_total_size; u += mtd_erasesize) {
David Hendricks85f61c52015-06-04 19:52:59 -0700516 int rc;
517 struct erase_info_user erase_info = {
518 .start = u,
519 .length = mtd_erasesize,
520 };
521
522 rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
523 if (rc < 0) {
524 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
525 return 1;
526 } else if (rc == 1) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800527 if (!start_found) {
David Hendricks85f61c52015-06-04 19:52:59 -0700528 start = erase_info.start;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800529 start_found = 1;
530 }
Brian Norris3ce4c472016-06-02 16:40:14 -0700531 len += mtd_erasesize;
David Hendricks85f61c52015-06-04 19:52:59 -0700532 } else if (rc == 0) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800533 if (start_found) {
Brian Norris3ce4c472016-06-02 16:40:14 -0700534 /* TODO: changes required for supporting non-contiguous locked regions */
535 break;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800536 }
David Hendricks85f61c52015-06-04 19:52:59 -0700537 }
538
David Hendricks85f61c52015-06-04 19:52:59 -0700539 }
540
Wei-Ning Huangca907072016-04-27 11:30:17 +0800541 msg_cinfo("WP: write protect is %s.\n",
Brian Norris3ce4c472016-06-02 16:40:14 -0700542 start_found ? "enabled": "disabled");
Wei-Ning Huangca907072016-04-27 11:30:17 +0800543 msg_pinfo("WP: write protect range: start=0x%08x, "
Brian Norris3ce4c472016-06-02 16:40:14 -0700544 "len=0x%08x\n", start, len);
Wei-Ning Huangca907072016-04-27 11:30:17 +0800545
David Hendricks85f61c52015-06-04 19:52:59 -0700546 return 0;
547}
548
549static struct wp wp_mtd = {
550 .list_ranges = mtd_wp_list_ranges,
551 .set_range = mtd_wp_set_range,
552 .enable = mtd_wp_enable_writeprotect,
553 .disable = mtd_wp_disable_writeprotect,
554 .wp_status = mtd_wp_status,
555};