blob: 4cc70c8efd0217b90ceee8830834fcc99a1ed3c8 [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
David Hendrickscebee892015-05-23 20:30:30 -070055static int stat_mtd_files(char *dev_path, char *sysfs_path)
56{
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
185static int linux_mtd_probe(struct flashchip *flash)
186{
David Hendricks85f61c52015-06-04 19:52:59 -0700187 flash->wp = &wp_mtd;
David Hendrickscebee892015-05-23 20:30:30 -0700188 flash->tested = TEST_OK_PREW;
189 flash->total_size = mtd_total_size / 1024; /* bytes -> kB */
190 flash->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
191 flash->block_erasers[0].eraseblocks[0].count =
192 mtd_total_size / mtd_erasesize;
193 return 1;
194}
195
196static int linux_mtd_read(struct flashchip *flash, uint8_t *buf,
197 unsigned int start, unsigned int len)
198{
199 if (lseek(dev_fd, start, SEEK_SET) != start) {
200 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
201 return 1;
202 }
203
204 if (read(dev_fd, buf, len) != len) {
205 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
206 start, len, strerror(errno));
207 return 1;
208 }
209
210 return 0;
211}
212
213/* this version assumes we must divide the write request into pages ourselves */
214static int linux_mtd_write(struct flashchip *flash, uint8_t *buf,
215 unsigned int start, unsigned int len)
216{
217 unsigned int page;
218 unsigned int chunksize, page_size;
219
220 chunksize = page_size = flash->page_size;
221
222 if (!mtd_device_is_writeable)
223 return 1;
224
225 for (page = start / page_size;
226 page <= (start + len - 1) / page_size; page++) {
227 unsigned int i, starthere, lenhere;
228
229 starthere = max(start, page * page_size);
230 lenhere = min(start + len, (page + 1) * page_size) - starthere;
231 for (i = 0; i < lenhere; i += chunksize) {
232 unsigned int towrite = min(chunksize, lenhere - i);
233
234 if (lseek(dev_fd, starthere, SEEK_SET) != starthere) {
235 msg_perr("Cannot seek to 0x%06x: %s\n",
236 start, strerror(errno));
237 return 1;
238 }
239
240 if (write(dev_fd, &buf[starthere - start], towrite) != towrite) {
241 msg_perr("Cannot read 0x%06x bytes at 0x%06x: "
242 "%s\n", start, len, strerror(errno));
243 return 1;
244 }
245 }
246 }
247
248 return 0;
249}
250
251static int linux_mtd_erase(struct flashchip *flash,
252 unsigned int start, unsigned int len)
253{
David Hendrickscebee892015-05-23 20:30:30 -0700254 uint32_t u;
255
256 if (mtd_numeraseregions != 0) {
257 /* TODO: Support non-uniform eraseblock size using
258 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
259 }
260
261 for (u = 0; u < len; u += mtd_erasesize) {
262 struct erase_info_user erase_info = {
263 .start = start + u,
264 .length = mtd_erasesize,
265 };
266
267 if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
268 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
269 return 1;
270 }
271 }
272
273 return 0;
274}
275
276static struct opaque_programmer programmer_linux_mtd = {
277 /* FIXME: Do we need to change these (especially write) as per
278 * page size requirements? */
279 .max_data_read = MAX_DATA_UNSPECIFIED,
280 .max_data_write = MAX_DATA_UNSPECIFIED,
281 .probe = linux_mtd_probe,
282 .read = linux_mtd_read,
283 .write = linux_mtd_write,
284 .erase = linux_mtd_erase,
285};
286
287/* Returns 0 if setup is successful, non-zero to indicate error */
288static int linux_mtd_setup(int dev_num)
289{
290 char dev_path[16]; /* "/dev/mtdN" */
291 int ret = 1;
292
293 if (dev_num < 0) {
294 char *tmp, *p;
295
296 tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
297 if (!tmp) {
David Hendrickscf6fbe62015-09-24 14:22:39 -0700298 msg_pdbg("%s: NOR type device not found.\n", __func__);
David Hendrickscebee892015-05-23 20:30:30 -0700299 goto linux_mtd_setup_exit;
300 }
301
302 /* "tmp" should be something like "/sys/blah/mtdN/type" */
303 p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
304 while (p[0] == '/')
305 p++;
306
307 if (sscanf(p, "mtd%d", &dev_num) != 1) {
308 msg_perr("Can't obtain device number from \"%s\"\n", p);
309 free(tmp);
310 goto linux_mtd_setup_exit;
311 }
312 free(tmp);
313 }
314
315 snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
316 LINUX_MTD_SYSFS_ROOT, dev_num);
317 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
318 LINUX_DEV_ROOT, dev_num);
319 msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
320 __func__, sysfs_path, dev_path);
321
322 if (stat_mtd_files(dev_path, sysfs_path))
323 goto linux_mtd_setup_exit;
324
325 if (get_mtd_info())
326 goto linux_mtd_setup_exit;
327
328 if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
329 msg_pdbg("%s: failed to open %s: %s\n", __func__,
330 dev_path, strerror(errno));
331 goto linux_mtd_setup_exit;
332 }
333
334 ret = 0;
335linux_mtd_setup_exit:
336 return ret;
337}
338
339static int linux_mtd_shutdown(void *data)
340{
341 if (dev_fd != -1) {
342 close(dev_fd);
343 dev_fd = -1;
344 }
345
346 return 0;
347}
348
349int linux_mtd_init(void)
350{
351 char *param;
352 int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
353 int ret = 1;
354
355 if (alias && alias->type != ALIAS_HOST)
356 return 1;
357
358 param = extract_programmer_param("dev");
359 if (param) {
360 char *endptr;
361
362 dev_num = strtol(param, &endptr, 0);
363 if ((param == endptr) || (dev_num < 0)) {
364 msg_perr("Invalid device number %s. Use flashrom -p "
365 "linux_mtd:dev=N where N is a valid MTD "
366 "device number\n", param);
367 goto linux_mtd_init_exit;
368 }
369 }
370
371 if (linux_mtd_setup(dev_num))
372 goto linux_mtd_init_exit;
373
374 if (register_shutdown(linux_mtd_shutdown, NULL))
375 goto linux_mtd_init_exit;
376
377 register_opaque_programmer(&programmer_linux_mtd);
378
379 ret = 0;
380linux_mtd_init_exit:
381 msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
382 return ret;
383}
David Hendricks85f61c52015-06-04 19:52:59 -0700384
385/*
386 * Write-protect functions.
387 */
388static int mtd_wp_list_ranges(const struct flashchip *flash)
389{
390 /* TODO: implement this */
391 msg_perr("--wp-list is not currently implemented for MTD.\n");
392 return 1;
393}
394
395/*
396 * We only have MEMLOCK to enable write-protection for a particular block,
397 * so we need to do force the user to use --wp-range and --wp-enable
398 * command-line arguments simultaneously. (Fortunately, CrOS factory
399 * installer does this already).
400 *
401 * The --wp-range argument is processed first and will set these variables
402 * which --wp-enable will use afterward.
403 */
404static unsigned int wp_range_start;
405static unsigned int wp_range_len;
406static int wp_set_range_called = 0;
407
408static int mtd_wp_set_range(const struct flashchip *flash,
409 unsigned int start, unsigned int len)
410{
411 wp_range_start = start;
412 wp_range_len = len;
413
414 wp_set_range_called = 1;
415 return 0;
416}
417
418static int mtd_wp_enable_writeprotect(const struct flashchip *flash, enum wp_mode mode)
419{
420 struct erase_info_user entire_chip = {
421 .start = 0,
422 .length = mtd_total_size,
423 };
424 struct erase_info_user desired_range = {
425 .start = wp_range_start,
426 .length = wp_range_len,
427 };
428
429 if (!wp_set_range_called) {
430 msg_perr("For MTD, --wp-range and --wp-enable must be "
431 "used simultaneously.\n");
432 return 1;
433 }
434
435 /*
436 * MTD handles write-protection additively, so whatever new range is
437 * specified is added to the range which is currently protected. To be
438 * consistent with flashrom behavior with other programmer interfaces,
439 * we need to disable the current write protection and then enable
440 * it for the desired range.
441 */
442 if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
443 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
444 __func__, strerror(errno));
445 return 1;
446 }
447
448 if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
449 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
450 __func__, strerror(errno));
451 return 1;
452 }
453
454 return 0;
455}
456
457static int mtd_wp_disable_writeprotect(const struct flashchip *flash)
458{
459 struct erase_info_user erase_info;
460
461 if (wp_set_range_called) {
462 erase_info.start = wp_range_start;
463 erase_info.length = wp_range_len;
464 } else {
465 erase_info.start = 0;
466 erase_info.length = mtd_total_size;
467 }
468
469 if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
470 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
471 return 1;
472 }
473
474 return 0;
475}
476
477static int mtd_wp_status(const struct flashchip *flash)
478{
479 uint32_t start = 0, end = 0;
480 unsigned int u;
481
482 /* For now, assume only one contiguous region can be locked (NOR) */
483 /* FIXME: use flash struct members instead of raw MTD values here */
484 for (u = 0; u < mtd_total_size / mtd_erasesize; u += mtd_erasesize) {
485 int rc;
486 struct erase_info_user erase_info = {
487 .start = u,
488 .length = mtd_erasesize,
489 };
490
491 rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
492 if (rc < 0) {
493 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
494 return 1;
495 } else if (rc == 1) {
496 if (!start)
497 start = erase_info.start;
498 } else if (rc == 0) {
499 if (start)
500 end = erase_info.start - 1;
501 }
502
503 if (start && end) {
504 msg_pinfo("WP: write protect range: start=0x%08x, "
505 "len=0x%08x\n", start, end - start);
506 /* TODO: Replace this break with "start = end = 0" if
507 * we want to support non-contiguous locked regions */
508 break;
509 }
510 }
511
512 return 0;
513}
514
515static struct wp wp_mtd = {
516 .list_ranges = mtd_wp_list_ranges,
517 .set_range = mtd_wp_set_range,
518 .enable = mtd_wp_enable_writeprotect,
519 .disable = mtd_wp_disable_writeprotect,
520 .wp_status = mtd_wp_status,
521};