blob: 3fecad049500ea6078e95cafabb5d2b00df85300 [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
48/* 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 }
148
149 /* Device name */
150 if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
151 return 1;
152
153 /* Total size */
154 if (read_sysfs_int("size", &mtd_total_size))
155 return 1;
156 if (__builtin_popcount(mtd_total_size) != 1) {
157 msg_perr("MTD size is not a power of 2\n");
158 return 1;
159 }
160
161 /* Erase size */
162 if (read_sysfs_int("erasesize", &mtd_erasesize))
163 return 1;
164 if (__builtin_popcount(mtd_erasesize) != 1) {
165 msg_perr("MTD erase size is not a power of 2\n");
166 return 1;
167 }
168
169 /* Erase regions */
170 if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
171 return 1;
172 if (mtd_numeraseregions != 0) {
173 msg_perr("Non-uniform eraseblock size is unsupported.\n");
174 return 1;
175 }
176
177 msg_pspew("%s: device_name: \"%s\", is_writeable: %d, "
178 "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
179 __func__, mtd_device_name, mtd_device_is_writeable,
180 mtd_numeraseregions, mtd_total_size, mtd_erasesize);
181
182 return 0;
183}
184
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700185static int linux_mtd_probe(struct flashctx *flash)
David Hendrickscebee892015-05-23 20:30:30 -0700186{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100187 flash->chip->wp = &wp_mtd;
188 flash->chip->tested = TEST_OK_PREW;
189 flash->chip->total_size = mtd_total_size / 1024; /* bytes -> kB */
190 flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
191 flash->chip->block_erasers[0].eraseblocks[0].count =
David Hendrickscebee892015-05-23 20:30:30 -0700192 mtd_total_size / mtd_erasesize;
193 return 1;
194}
195
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700196static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700197 unsigned int start, unsigned int len)
198{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100199 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
Brian Norris99c8ad22016-05-10 15:36:02 -0700200 unsigned int i;
201
David Hendrickscebee892015-05-23 20:30:30 -0700202 if (lseek(dev_fd, start, SEEK_SET) != start) {
203 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
204 return 1;
205 }
206
Brian Norris99c8ad22016-05-10 15:36:02 -0700207 for (i = 0; i < len; ) {
208 /* Try to align reads to eraseblock size */
209 unsigned int step = eb_size - ((start + i) % eb_size);
210 step = min(step, len - i);
211
212 if (read(dev_fd, buf + i, step) != step) {
213 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
214 step, start + i, strerror(errno));
215 return 1;
216 }
217
218 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700219 }
220
221 return 0;
222}
223
224/* this version assumes we must divide the write request into pages ourselves */
Patrick Georgiab8353e2017-02-03 18:32:01 +0100225static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700226 unsigned int start, unsigned int len)
227{
228 unsigned int page;
229 unsigned int chunksize, page_size;
230
Patrick Georgif3fa2992017-02-02 16:24:44 +0100231 chunksize = page_size = flash->chip->page_size;
David Hendrickscebee892015-05-23 20:30:30 -0700232
233 if (!mtd_device_is_writeable)
234 return 1;
235
236 for (page = start / page_size;
237 page <= (start + len - 1) / page_size; page++) {
238 unsigned int i, starthere, lenhere;
239
240 starthere = max(start, page * page_size);
241 lenhere = min(start + len, (page + 1) * page_size) - starthere;
242 for (i = 0; i < lenhere; i += chunksize) {
243 unsigned int towrite = min(chunksize, lenhere - i);
244
245 if (lseek(dev_fd, starthere, SEEK_SET) != starthere) {
246 msg_perr("Cannot seek to 0x%06x: %s\n",
247 start, strerror(errno));
248 return 1;
249 }
250
251 if (write(dev_fd, &buf[starthere - start], towrite) != towrite) {
252 msg_perr("Cannot read 0x%06x bytes at 0x%06x: "
253 "%s\n", start, len, strerror(errno));
254 return 1;
255 }
256 }
257 }
258
259 return 0;
260}
261
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700262static int linux_mtd_erase(struct flashctx *flash,
David Hendrickscebee892015-05-23 20:30:30 -0700263 unsigned int start, unsigned int len)
264{
David Hendrickscebee892015-05-23 20:30:30 -0700265 uint32_t u;
266
267 if (mtd_numeraseregions != 0) {
268 /* TODO: Support non-uniform eraseblock size using
269 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
270 }
271
272 for (u = 0; u < len; u += mtd_erasesize) {
273 struct erase_info_user erase_info = {
274 .start = start + u,
275 .length = mtd_erasesize,
276 };
277
278 if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
279 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
280 return 1;
281 }
282 }
283
284 return 0;
285}
286
287static struct opaque_programmer programmer_linux_mtd = {
Brian Norris99c8ad22016-05-10 15:36:02 -0700288 /* max_data_{read,write} don't have any effect for this programmer */
David Hendrickscebee892015-05-23 20:30:30 -0700289 .max_data_read = MAX_DATA_UNSPECIFIED,
290 .max_data_write = MAX_DATA_UNSPECIFIED,
291 .probe = linux_mtd_probe,
292 .read = linux_mtd_read,
293 .write = linux_mtd_write,
294 .erase = linux_mtd_erase,
295};
296
297/* Returns 0 if setup is successful, non-zero to indicate error */
298static int linux_mtd_setup(int dev_num)
299{
300 char dev_path[16]; /* "/dev/mtdN" */
301 int ret = 1;
302
303 if (dev_num < 0) {
304 char *tmp, *p;
305
306 tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
307 if (!tmp) {
David Hendrickscf6fbe62015-09-24 14:22:39 -0700308 msg_pdbg("%s: NOR type device not found.\n", __func__);
David Hendrickscebee892015-05-23 20:30:30 -0700309 goto linux_mtd_setup_exit;
310 }
311
312 /* "tmp" should be something like "/sys/blah/mtdN/type" */
313 p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
314 while (p[0] == '/')
315 p++;
316
317 if (sscanf(p, "mtd%d", &dev_num) != 1) {
318 msg_perr("Can't obtain device number from \"%s\"\n", p);
319 free(tmp);
320 goto linux_mtd_setup_exit;
321 }
322 free(tmp);
323 }
324
325 snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
326 LINUX_MTD_SYSFS_ROOT, dev_num);
327 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
328 LINUX_DEV_ROOT, dev_num);
329 msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
330 __func__, sysfs_path, dev_path);
331
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -0700332 if (stat_mtd_files(dev_path))
David Hendrickscebee892015-05-23 20:30:30 -0700333 goto linux_mtd_setup_exit;
334
335 if (get_mtd_info())
336 goto linux_mtd_setup_exit;
337
338 if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
339 msg_pdbg("%s: failed to open %s: %s\n", __func__,
340 dev_path, strerror(errno));
341 goto linux_mtd_setup_exit;
342 }
343
344 ret = 0;
345linux_mtd_setup_exit:
346 return ret;
347}
348
David Hendricks93784b42016-08-09 17:00:38 -0700349static int linux_mtd_shutdown(void *data)
David Hendrickscebee892015-05-23 20:30:30 -0700350{
351 if (dev_fd != -1) {
352 close(dev_fd);
353 dev_fd = -1;
354 }
355
356 return 0;
357}
358
David Hendricksac1d25c2016-08-09 17:00:58 -0700359int linux_mtd_init(void)
David Hendrickscebee892015-05-23 20:30:30 -0700360{
361 char *param;
362 int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
363 int ret = 1;
364
365 if (alias && alias->type != ALIAS_HOST)
366 return 1;
367
368 param = extract_programmer_param("dev");
369 if (param) {
370 char *endptr;
371
372 dev_num = strtol(param, &endptr, 0);
373 if ((param == endptr) || (dev_num < 0)) {
374 msg_perr("Invalid device number %s. Use flashrom -p "
375 "linux_mtd:dev=N where N is a valid MTD "
376 "device number\n", param);
377 goto linux_mtd_init_exit;
378 }
379 }
380
381 if (linux_mtd_setup(dev_num))
382 goto linux_mtd_init_exit;
383
384 if (register_shutdown(linux_mtd_shutdown, NULL))
385 goto linux_mtd_init_exit;
386
387 register_opaque_programmer(&programmer_linux_mtd);
388
389 ret = 0;
390linux_mtd_init_exit:
391 msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
392 return ret;
393}
David Hendricks85f61c52015-06-04 19:52:59 -0700394
395/*
396 * Write-protect functions.
397 */
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700398static int mtd_wp_list_ranges(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700399{
400 /* TODO: implement this */
401 msg_perr("--wp-list is not currently implemented for MTD.\n");
402 return 1;
403}
404
405/*
406 * We only have MEMLOCK to enable write-protection for a particular block,
407 * so we need to do force the user to use --wp-range and --wp-enable
408 * command-line arguments simultaneously. (Fortunately, CrOS factory
409 * installer does this already).
410 *
411 * The --wp-range argument is processed first and will set these variables
412 * which --wp-enable will use afterward.
413 */
414static unsigned int wp_range_start;
415static unsigned int wp_range_len;
416static int wp_set_range_called = 0;
417
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700418static int mtd_wp_set_range(const struct flashctx *flash,
David Hendricks85f61c52015-06-04 19:52:59 -0700419 unsigned int start, unsigned int len)
420{
421 wp_range_start = start;
422 wp_range_len = len;
423
424 wp_set_range_called = 1;
425 return 0;
426}
427
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700428static int mtd_wp_enable_writeprotect(const struct flashctx *flash, enum wp_mode mode)
David Hendricks85f61c52015-06-04 19:52:59 -0700429{
430 struct erase_info_user entire_chip = {
431 .start = 0,
432 .length = mtd_total_size,
433 };
434 struct erase_info_user desired_range = {
435 .start = wp_range_start,
436 .length = wp_range_len,
437 };
438
439 if (!wp_set_range_called) {
440 msg_perr("For MTD, --wp-range and --wp-enable must be "
441 "used simultaneously.\n");
442 return 1;
443 }
444
445 /*
446 * MTD handles write-protection additively, so whatever new range is
447 * specified is added to the range which is currently protected. To be
448 * consistent with flashrom behavior with other programmer interfaces,
449 * we need to disable the current write protection and then enable
450 * it for the desired range.
451 */
452 if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
453 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
454 __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700455 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700456 return 1;
457 }
458
459 if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
460 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
461 __func__, strerror(errno));
462 return 1;
463 }
464
465 return 0;
466}
467
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700468static int mtd_wp_disable_writeprotect(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700469{
470 struct erase_info_user erase_info;
471
472 if (wp_set_range_called) {
473 erase_info.start = wp_range_start;
474 erase_info.length = wp_range_len;
475 } else {
476 erase_info.start = 0;
477 erase_info.length = mtd_total_size;
478 }
479
480 if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
481 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700482 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700483 return 1;
484 }
485
486 return 0;
487}
488
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700489static int mtd_wp_status(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700490{
Brian Norris3ce4c472016-06-02 16:40:14 -0700491 uint32_t start = 0, len = 0;
492 int start_found = 0;
David Hendricks85f61c52015-06-04 19:52:59 -0700493 unsigned int u;
494
495 /* For now, assume only one contiguous region can be locked (NOR) */
496 /* FIXME: use flash struct members instead of raw MTD values here */
Wei-Ning Huang699269b2016-03-02 22:08:05 +0800497 for (u = 0; u < mtd_total_size; u += mtd_erasesize) {
David Hendricks85f61c52015-06-04 19:52:59 -0700498 int rc;
499 struct erase_info_user erase_info = {
500 .start = u,
501 .length = mtd_erasesize,
502 };
503
504 rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
505 if (rc < 0) {
506 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
507 return 1;
508 } else if (rc == 1) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800509 if (!start_found) {
David Hendricks85f61c52015-06-04 19:52:59 -0700510 start = erase_info.start;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800511 start_found = 1;
512 }
Brian Norris3ce4c472016-06-02 16:40:14 -0700513 len += mtd_erasesize;
David Hendricks85f61c52015-06-04 19:52:59 -0700514 } else if (rc == 0) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800515 if (start_found) {
Brian Norris3ce4c472016-06-02 16:40:14 -0700516 /* TODO: changes required for supporting non-contiguous locked regions */
517 break;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800518 }
David Hendricks85f61c52015-06-04 19:52:59 -0700519 }
520
David Hendricks85f61c52015-06-04 19:52:59 -0700521 }
522
Wei-Ning Huangca907072016-04-27 11:30:17 +0800523 msg_cinfo("WP: write protect is %s.\n",
Brian Norris3ce4c472016-06-02 16:40:14 -0700524 start_found ? "enabled": "disabled");
Wei-Ning Huangca907072016-04-27 11:30:17 +0800525 msg_pinfo("WP: write protect range: start=0x%08x, "
Brian Norris3ce4c472016-06-02 16:40:14 -0700526 "len=0x%08x\n", start, len);
Wei-Ning Huangca907072016-04-27 11:30:17 +0800527
David Hendricks85f61c52015-06-04 19:52:59 -0700528 return 0;
529}
530
531static struct wp wp_mtd = {
532 .list_ranges = mtd_wp_list_ranges,
533 .set_range = mtd_wp_set_range,
534 .enable = mtd_wp_enable_writeprotect,
535 .disable = mtd_wp_disable_writeprotect,
536 .wp_status = mtd_wp_status,
537};