blob: e0358ea967d1bd3df102543a7b67050694b07aec [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
46static char *mtd_device_name;
47static int mtd_device_is_writeable;
48
49/* Size info is presented in bytes in sysfs. */
50static unsigned long int mtd_total_size;
51static unsigned long int mtd_numeraseregions;
52static unsigned long int mtd_erasesize; /* only valid if numeraseregions is 0 */
53
David Hendricks85f61c52015-06-04 19:52:59 -070054static struct wp wp_mtd; /* forward declaration */
55
David Hendrickscebee892015-05-23 20:30:30 -070056static int stat_mtd_files(char *dev_path, char *sysfs_path)
57{
58 struct stat s;
59
60 errno = 0;
61 if (stat(dev_path, &s) < 0) {
62 msg_pdbg("Cannot stat \"%s\": %s\n", dev_path, strerror(errno));
63 return 1;
64 }
65
66 if (lstat(sysfs_path, &s) < 0) {
67 msg_pdbg("Cannot stat \"%s\" : %s\n",
68 sysfs_path, strerror(errno));
69 return 1;
70 }
71
72 return 0;
73}
74
75/* read a string from a sysfs file and sanitize it */
76static int read_sysfs_string(const char *filename, char *buf, int len)
77{
78 int fd, bytes_read, i;
79 char path[strlen(LINUX_MTD_SYSFS_ROOT) + 32];
80
81 snprintf(path, sizeof(path), "%s/%s", sysfs_path, filename);
82
83 if ((fd = open(path, O_RDONLY)) < 0) {
84 msg_perr("Cannot open %s\n", path);
85 return 1;
86 }
87
88 if ((bytes_read = read(fd, buf, len - 1)) < 0) {
89 msg_perr("Cannot read %s\n", path);
90 close(fd);
91 return 1;
92 }
93
94 buf[bytes_read] = '\0';
95
96 /*
97 * Files from sysfs sometimes contain a newline or other garbage that
98 * can confuse functions like strtoul() and ruin formatting in print
99 * statements. Replace the first non-printable character (space is
100 * considered printable) with a proper string terminator.
101 */
102 for (i = 0; i < len; i++) {
103 if (!isprint(buf[i])) {
104 buf[i] = '\0';
105 break;
106 }
107 }
108
109 close(fd);
110 return 0;
111}
112
113static int read_sysfs_int(const char *filename, unsigned long int *val)
114{
115 uint32_t tmp;
116 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 }
150
151 /* Device name */
152 if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
153 return 1;
154
155 /* Total size */
156 if (read_sysfs_int("size", &mtd_total_size))
157 return 1;
158 if (__builtin_popcount(mtd_total_size) != 1) {
159 msg_perr("MTD size is not a power of 2\n");
160 return 1;
161 }
162
163 /* Erase size */
164 if (read_sysfs_int("erasesize", &mtd_erasesize))
165 return 1;
166 if (__builtin_popcount(mtd_erasesize) != 1) {
167 msg_perr("MTD erase size is not a power of 2\n");
168 return 1;
169 }
170
171 /* Erase regions */
172 if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
173 return 1;
174 if (mtd_numeraseregions != 0) {
175 msg_perr("Non-uniform eraseblock size is unsupported.\n");
176 return 1;
177 }
178
179 msg_pspew("%s: device_name: \"%s\", is_writeable: %d, "
180 "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
181 __func__, mtd_device_name, mtd_device_is_writeable,
182 mtd_numeraseregions, mtd_total_size, mtd_erasesize);
183
184 return 0;
185}
186
187static int linux_mtd_probe(struct flashchip *flash)
188{
David Hendricks85f61c52015-06-04 19:52:59 -0700189 flash->wp = &wp_mtd;
David Hendrickscebee892015-05-23 20:30:30 -0700190 flash->tested = TEST_OK_PREW;
191 flash->total_size = mtd_total_size / 1024; /* bytes -> kB */
192 flash->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
193 flash->block_erasers[0].eraseblocks[0].count =
194 mtd_total_size / mtd_erasesize;
195 return 1;
196}
197
198static int linux_mtd_read(struct flashchip *flash, uint8_t *buf,
199 unsigned int start, unsigned int len)
200{
201 if (lseek(dev_fd, start, SEEK_SET) != start) {
202 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
203 return 1;
204 }
205
206 if (read(dev_fd, buf, len) != len) {
207 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
208 start, len, strerror(errno));
209 return 1;
210 }
211
212 return 0;
213}
214
215/* this version assumes we must divide the write request into pages ourselves */
216static int linux_mtd_write(struct flashchip *flash, uint8_t *buf,
217 unsigned int start, unsigned int len)
218{
219 unsigned int page;
220 unsigned int chunksize, page_size;
221
222 chunksize = page_size = flash->page_size;
223
224 if (!mtd_device_is_writeable)
225 return 1;
226
227 for (page = start / page_size;
228 page <= (start + len - 1) / page_size; page++) {
229 unsigned int i, starthere, lenhere;
230
231 starthere = max(start, page * page_size);
232 lenhere = min(start + len, (page + 1) * page_size) - starthere;
233 for (i = 0; i < lenhere; i += chunksize) {
234 unsigned int towrite = min(chunksize, lenhere - i);
235
236 if (lseek(dev_fd, starthere, SEEK_SET) != starthere) {
237 msg_perr("Cannot seek to 0x%06x: %s\n",
238 start, strerror(errno));
239 return 1;
240 }
241
242 if (write(dev_fd, &buf[starthere - start], towrite) != towrite) {
243 msg_perr("Cannot read 0x%06x bytes at 0x%06x: "
244 "%s\n", start, len, strerror(errno));
245 return 1;
246 }
247 }
248 }
249
250 return 0;
251}
252
253static int linux_mtd_erase(struct flashchip *flash,
254 unsigned int start, unsigned int len)
255{
256 struct region_info_user region_info;
257 uint32_t u;
258
259 if (mtd_numeraseregions != 0) {
260 /* TODO: Support non-uniform eraseblock size using
261 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
262 }
263
264 for (u = 0; u < len; u += mtd_erasesize) {
265 struct erase_info_user erase_info = {
266 .start = start + u,
267 .length = mtd_erasesize,
268 };
269
270 if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
271 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
272 return 1;
273 }
274 }
275
276 return 0;
277}
278
279static struct opaque_programmer programmer_linux_mtd = {
280 /* FIXME: Do we need to change these (especially write) as per
281 * page size requirements? */
282 .max_data_read = MAX_DATA_UNSPECIFIED,
283 .max_data_write = MAX_DATA_UNSPECIFIED,
284 .probe = linux_mtd_probe,
285 .read = linux_mtd_read,
286 .write = linux_mtd_write,
287 .erase = linux_mtd_erase,
288};
289
290/* Returns 0 if setup is successful, non-zero to indicate error */
291static int linux_mtd_setup(int dev_num)
292{
293 char dev_path[16]; /* "/dev/mtdN" */
294 int ret = 1;
295
296 if (dev_num < 0) {
297 char *tmp, *p;
298
299 tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
300 if (!tmp) {
David Hendrickscf6fbe62015-09-24 14:22:39 -0700301 msg_pdbg("%s: NOR type device not found.\n", __func__);
David Hendrickscebee892015-05-23 20:30:30 -0700302 goto linux_mtd_setup_exit;
303 }
304
305 /* "tmp" should be something like "/sys/blah/mtdN/type" */
306 p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
307 while (p[0] == '/')
308 p++;
309
310 if (sscanf(p, "mtd%d", &dev_num) != 1) {
311 msg_perr("Can't obtain device number from \"%s\"\n", p);
312 free(tmp);
313 goto linux_mtd_setup_exit;
314 }
315 free(tmp);
316 }
317
318 snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
319 LINUX_MTD_SYSFS_ROOT, dev_num);
320 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
321 LINUX_DEV_ROOT, dev_num);
322 msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
323 __func__, sysfs_path, dev_path);
324
325 if (stat_mtd_files(dev_path, sysfs_path))
326 goto linux_mtd_setup_exit;
327
328 if (get_mtd_info())
329 goto linux_mtd_setup_exit;
330
331 if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
332 msg_pdbg("%s: failed to open %s: %s\n", __func__,
333 dev_path, strerror(errno));
334 goto linux_mtd_setup_exit;
335 }
336
337 ret = 0;
338linux_mtd_setup_exit:
339 return ret;
340}
341
342static int linux_mtd_shutdown(void *data)
343{
344 if (dev_fd != -1) {
345 close(dev_fd);
346 dev_fd = -1;
347 }
348
349 return 0;
350}
351
352int linux_mtd_init(void)
353{
354 char *param;
355 int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
356 int ret = 1;
357
358 if (alias && alias->type != ALIAS_HOST)
359 return 1;
360
361 param = extract_programmer_param("dev");
362 if (param) {
363 char *endptr;
364
365 dev_num = strtol(param, &endptr, 0);
366 if ((param == endptr) || (dev_num < 0)) {
367 msg_perr("Invalid device number %s. Use flashrom -p "
368 "linux_mtd:dev=N where N is a valid MTD "
369 "device number\n", param);
370 goto linux_mtd_init_exit;
371 }
372 }
373
374 if (linux_mtd_setup(dev_num))
375 goto linux_mtd_init_exit;
376
377 if (register_shutdown(linux_mtd_shutdown, NULL))
378 goto linux_mtd_init_exit;
379
380 register_opaque_programmer(&programmer_linux_mtd);
381
382 ret = 0;
383linux_mtd_init_exit:
384 msg_pdbg("%s: %s\n", __func__, ret == 0 ? "success." : "failed.");
385 return ret;
386}
David Hendricks85f61c52015-06-04 19:52:59 -0700387
388/*
389 * Write-protect functions.
390 */
391static int mtd_wp_list_ranges(const struct flashchip *flash)
392{
393 /* TODO: implement this */
394 msg_perr("--wp-list is not currently implemented for MTD.\n");
395 return 1;
396}
397
398/*
399 * We only have MEMLOCK to enable write-protection for a particular block,
400 * so we need to do force the user to use --wp-range and --wp-enable
401 * command-line arguments simultaneously. (Fortunately, CrOS factory
402 * installer does this already).
403 *
404 * The --wp-range argument is processed first and will set these variables
405 * which --wp-enable will use afterward.
406 */
407static unsigned int wp_range_start;
408static unsigned int wp_range_len;
409static int wp_set_range_called = 0;
410
411static int mtd_wp_set_range(const struct flashchip *flash,
412 unsigned int start, unsigned int len)
413{
414 wp_range_start = start;
415 wp_range_len = len;
416
417 wp_set_range_called = 1;
418 return 0;
419}
420
421static int mtd_wp_enable_writeprotect(const struct flashchip *flash, enum wp_mode mode)
422{
423 struct erase_info_user entire_chip = {
424 .start = 0,
425 .length = mtd_total_size,
426 };
427 struct erase_info_user desired_range = {
428 .start = wp_range_start,
429 .length = wp_range_len,
430 };
431
432 if (!wp_set_range_called) {
433 msg_perr("For MTD, --wp-range and --wp-enable must be "
434 "used simultaneously.\n");
435 return 1;
436 }
437
438 /*
439 * MTD handles write-protection additively, so whatever new range is
440 * specified is added to the range which is currently protected. To be
441 * consistent with flashrom behavior with other programmer interfaces,
442 * we need to disable the current write protection and then enable
443 * it for the desired range.
444 */
445 if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
446 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
447 __func__, strerror(errno));
448 return 1;
449 }
450
451 if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
452 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
453 __func__, strerror(errno));
454 return 1;
455 }
456
457 return 0;
458}
459
460static int mtd_wp_disable_writeprotect(const struct flashchip *flash)
461{
462 struct erase_info_user erase_info;
463
464 if (wp_set_range_called) {
465 erase_info.start = wp_range_start;
466 erase_info.length = wp_range_len;
467 } else {
468 erase_info.start = 0;
469 erase_info.length = mtd_total_size;
470 }
471
472 if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
473 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
474 return 1;
475 }
476
477 return 0;
478}
479
480static int mtd_wp_status(const struct flashchip *flash)
481{
482 uint32_t start = 0, end = 0;
483 unsigned int u;
484
485 /* For now, assume only one contiguous region can be locked (NOR) */
486 /* FIXME: use flash struct members instead of raw MTD values here */
487 for (u = 0; u < mtd_total_size / mtd_erasesize; u += mtd_erasesize) {
488 int rc;
489 struct erase_info_user erase_info = {
490 .start = u,
491 .length = mtd_erasesize,
492 };
493
494 rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
495 if (rc < 0) {
496 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
497 return 1;
498 } else if (rc == 1) {
499 if (!start)
500 start = erase_info.start;
501 } else if (rc == 0) {
502 if (start)
503 end = erase_info.start - 1;
504 }
505
506 if (start && end) {
507 msg_pinfo("WP: write protect range: start=0x%08x, "
508 "len=0x%08x\n", start, end - start);
509 /* TODO: Replace this break with "start = end = 0" if
510 * we want to support non-contiguous locked regions */
511 break;
512 }
513 }
514
515 return 0;
516}
517
518static struct wp wp_mtd = {
519 .list_ranges = mtd_wp_list_ranges,
520 .set_range = mtd_wp_set_range,
521 .enable = mtd_wp_enable_writeprotect,
522 .disable = mtd_wp_disable_writeprotect,
523 .wp_status = mtd_wp_status,
524};