blob: 2a94df23de33a7dcbe489cb2d333ec21fdd067fd [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.
Nikolai Artemiev255f3352020-07-17 16:38:50 +10005 * Copyright 2018-present Facebook, Inc.
David Hendrickscebee892015-05-23 20:30:30 -07006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 2 of the License.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
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);
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000122 if (*endptr != '\0') {
David Hendrickscebee892015-05-23 20:30:30 -0700123 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
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000135static int popcnt(unsigned int u)
136{
137 int count = 0;
138
139 while (u) {
140 u &= u - 1;
141 count++;
142 }
143
144 return count;
145}
146
David Hendrickscebee892015-05-23 20:30:30 -0700147/* returns 0 to indicate success, non-zero to indicate error */
148static int get_mtd_info(void)
149{
150 unsigned long int tmp;
151 char mtd_device_name[32];
152
153 /* Flags */
154 if (read_sysfs_int("flags", &tmp))
155 return 1;
156 if (tmp & MTD_WRITEABLE) {
157 /* cache for later use by write function */
158 mtd_device_is_writeable = 1;
159 }
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700160 if (tmp & MTD_NO_ERASE) {
161 mtd_no_erase = 1;
162 }
David Hendrickscebee892015-05-23 20:30:30 -0700163
164 /* Device name */
165 if (read_sysfs_string("name", mtd_device_name, sizeof(mtd_device_name)))
166 return 1;
167
168 /* Total size */
169 if (read_sysfs_int("size", &mtd_total_size))
170 return 1;
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000171 if (popcnt(mtd_total_size) != 1) {
David Hendrickscebee892015-05-23 20:30:30 -0700172 msg_perr("MTD size is not a power of 2\n");
173 return 1;
174 }
175
176 /* Erase size */
177 if (read_sysfs_int("erasesize", &mtd_erasesize))
178 return 1;
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000179 if (popcnt(mtd_erasesize) != 1) {
David Hendrickscebee892015-05-23 20:30:30 -0700180 msg_perr("MTD erase size is not a power of 2\n");
181 return 1;
182 }
183
184 /* Erase regions */
185 if (read_sysfs_int("numeraseregions", &mtd_numeraseregions))
186 return 1;
187 if (mtd_numeraseregions != 0) {
188 msg_perr("Non-uniform eraseblock size is unsupported.\n");
189 return 1;
190 }
191
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000192 msg_pdbg("%s: device_name: \"%s\", is_writeable: %d, "
David Hendrickscebee892015-05-23 20:30:30 -0700193 "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n",
194 __func__, mtd_device_name, mtd_device_is_writeable,
195 mtd_numeraseregions, mtd_total_size, mtd_erasesize);
196
197 return 0;
198}
199
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700200static int linux_mtd_probe(struct flashctx *flash)
David Hendrickscebee892015-05-23 20:30:30 -0700201{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100202 flash->chip->wp = &wp_mtd;
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700203 if (mtd_no_erase)
204 flash->chip->feature_bits |= FEATURE_NO_ERASE;
Patrick Georgif3fa2992017-02-02 16:24:44 +0100205 flash->chip->tested = TEST_OK_PREW;
206 flash->chip->total_size = mtd_total_size / 1024; /* bytes -> kB */
207 flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize;
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000208 flash->chip->block_erasers[0].eraseblocks[0].count = mtd_total_size / mtd_erasesize;
David Hendrickscebee892015-05-23 20:30:30 -0700209 return 1;
210}
211
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700212static int linux_mtd_read(struct flashctx *flash, uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700213 unsigned int start, unsigned int len)
214{
Patrick Georgif3fa2992017-02-02 16:24:44 +0100215 unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size;
Brian Norris99c8ad22016-05-10 15:36:02 -0700216 unsigned int i;
217
David Hendrickscebee892015-05-23 20:30:30 -0700218 if (lseek(dev_fd, start, SEEK_SET) != start) {
219 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
220 return 1;
221 }
222
Brian Norris99c8ad22016-05-10 15:36:02 -0700223 for (i = 0; i < len; ) {
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700224 /*
225 * Try to align reads to eraseblock size.
226 * FIXME: Shouldn't actually be necessary, but not all MTD
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000227 * drivers handle arbitrary large reads well.
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700228 */
Brian Norris99c8ad22016-05-10 15:36:02 -0700229 unsigned int step = eb_size - ((start + i) % eb_size);
230 step = min(step, len - i);
231
232 if (read(dev_fd, buf + i, step) != step) {
233 msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n",
234 step, start + i, strerror(errno));
235 return 1;
236 }
237
238 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700239 }
240
241 return 0;
242}
243
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700244/* this version assumes we must divide the write request into chunks ourselves */
Patrick Georgiab8353e2017-02-03 18:32:01 +0100245static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf,
David Hendrickscebee892015-05-23 20:30:30 -0700246 unsigned int start, unsigned int len)
247{
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700248 unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size;
249 unsigned int i;
David Hendrickscebee892015-05-23 20:30:30 -0700250
251 if (!mtd_device_is_writeable)
252 return 1;
253
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700254 if (lseek(dev_fd, start, SEEK_SET) != start) {
255 msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno));
256 return 1;
257 }
David Hendrickscebee892015-05-23 20:30:30 -0700258
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700259 /*
260 * Try to align writes to eraseblock size. We want these large enough
261 * to give MTD room for optimizing performance.
262 * FIXME: Shouldn't need to divide this up at all, but not all MTD
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000263 * drivers handle arbitrary large writes well.
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700264 */
265 for (i = 0; i < len; ) {
266 unsigned int step = chunksize - ((start + i) % chunksize);
267 step = min(step, len - i);
David Hendrickscebee892015-05-23 20:30:30 -0700268
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700269 if (write(dev_fd, buf + i, step) != step) {
270 msg_perr("Cannot write 0x%06x bytes at 0x%06x: %s\n",
271 step, start + i, strerror(errno));
272 return 1;
David Hendrickscebee892015-05-23 20:30:30 -0700273 }
Brian Norris8bbb6dd2017-05-12 12:49:59 -0700274
275 i += step;
David Hendrickscebee892015-05-23 20:30:30 -0700276 }
277
278 return 0;
279}
280
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700281static int linux_mtd_erase(struct flashctx *flash,
David Hendrickscebee892015-05-23 20:30:30 -0700282 unsigned int start, unsigned int len)
283{
David Hendrickscebee892015-05-23 20:30:30 -0700284 uint32_t u;
285
William A. Kennington IIIf15c2fa2017-04-07 17:38:42 -0700286 if (mtd_no_erase) {
287 msg_perr("%s: device does not support erasing. Please file a "
288 "bug report at flashrom@flashrom.org\n", __func__);
289 return 1;
290 }
291
David Hendrickscebee892015-05-23 20:30:30 -0700292 if (mtd_numeraseregions != 0) {
293 /* TODO: Support non-uniform eraseblock size using
294 use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */
295 }
296
297 for (u = 0; u < len; u += mtd_erasesize) {
298 struct erase_info_user erase_info = {
299 .start = start + u,
300 .length = mtd_erasesize,
301 };
302
303 if (ioctl(dev_fd, MEMERASE, &erase_info) == -1) {
304 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
305 return 1;
306 }
307 }
308
309 return 0;
310}
311
Edward O'Callaghanabd30192019-05-14 15:58:19 +1000312static struct opaque_master programmer_linux_mtd = {
Brian Norris99c8ad22016-05-10 15:36:02 -0700313 /* max_data_{read,write} don't have any effect for this programmer */
David Hendrickscebee892015-05-23 20:30:30 -0700314 .max_data_read = MAX_DATA_UNSPECIFIED,
315 .max_data_write = MAX_DATA_UNSPECIFIED,
316 .probe = linux_mtd_probe,
317 .read = linux_mtd_read,
318 .write = linux_mtd_write,
319 .erase = linux_mtd_erase,
320};
321
322/* Returns 0 if setup is successful, non-zero to indicate error */
323static int linux_mtd_setup(int dev_num)
324{
325 char dev_path[16]; /* "/dev/mtdN" */
326 int ret = 1;
327
328 if (dev_num < 0) {
329 char *tmp, *p;
330
331 tmp = (char *)scanft(LINUX_MTD_SYSFS_ROOT, "type", "nor", 1);
332 if (!tmp) {
David Hendrickscf6fbe62015-09-24 14:22:39 -0700333 msg_pdbg("%s: NOR type device not found.\n", __func__);
David Hendrickscebee892015-05-23 20:30:30 -0700334 goto linux_mtd_setup_exit;
335 }
336
337 /* "tmp" should be something like "/sys/blah/mtdN/type" */
338 p = tmp + strlen(LINUX_MTD_SYSFS_ROOT);
339 while (p[0] == '/')
340 p++;
341
342 if (sscanf(p, "mtd%d", &dev_num) != 1) {
343 msg_perr("Can't obtain device number from \"%s\"\n", p);
344 free(tmp);
345 goto linux_mtd_setup_exit;
346 }
347 free(tmp);
348 }
349
350 snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d",
351 LINUX_MTD_SYSFS_ROOT, dev_num);
352 snprintf(dev_path, sizeof(dev_path), "%s/mtd%d",
353 LINUX_DEV_ROOT, dev_num);
354 msg_pdbg("%s: sysfs_path: \"%s\", dev_path: \"%s\"\n",
355 __func__, sysfs_path, dev_path);
356
Souvik Ghosh5cdb7e52016-06-23 12:57:38 -0700357 if (stat_mtd_files(dev_path))
David Hendrickscebee892015-05-23 20:30:30 -0700358 goto linux_mtd_setup_exit;
359
360 if (get_mtd_info())
361 goto linux_mtd_setup_exit;
362
363 if ((dev_fd = open(dev_path, O_RDWR)) == -1) {
364 msg_pdbg("%s: failed to open %s: %s\n", __func__,
365 dev_path, strerror(errno));
366 goto linux_mtd_setup_exit;
367 }
368
369 ret = 0;
370linux_mtd_setup_exit:
371 return ret;
372}
373
David Hendricks93784b42016-08-09 17:00:38 -0700374static int linux_mtd_shutdown(void *data)
David Hendrickscebee892015-05-23 20:30:30 -0700375{
376 if (dev_fd != -1) {
377 close(dev_fd);
378 dev_fd = -1;
379 }
380
381 return 0;
382}
383
David Hendricksac1d25c2016-08-09 17:00:58 -0700384int linux_mtd_init(void)
David Hendrickscebee892015-05-23 20:30:30 -0700385{
386 char *param;
387 int dev_num = -1; /* linux_mtd_setup will search if dev_num < 0 */
388 int ret = 1;
389
390 if (alias && alias->type != ALIAS_HOST)
391 return 1;
392
393 param = extract_programmer_param("dev");
394 if (param) {
395 char *endptr;
396
397 dev_num = strtol(param, &endptr, 0);
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000398 if ((*endptr != '\0') || (dev_num < 0)) {
David Hendrickscebee892015-05-23 20:30:30 -0700399 msg_perr("Invalid device number %s. Use flashrom -p "
Nikolai Artemiev255f3352020-07-17 16:38:50 +1000400 "linux_mtd:dev=N where N is a valid MTD\n"
401 "device number.\n", param);
David Hendrickscebee892015-05-23 20:30:30 -0700402 goto linux_mtd_init_exit;
403 }
404 }
405
406 if (linux_mtd_setup(dev_num))
407 goto linux_mtd_init_exit;
408
409 if (register_shutdown(linux_mtd_shutdown, NULL))
410 goto linux_mtd_init_exit;
411
Edward O'Callaghanabd30192019-05-14 15:58:19 +1000412 register_opaque_master(&programmer_linux_mtd);
David Hendrickscebee892015-05-23 20:30:30 -0700413
414 ret = 0;
415linux_mtd_init_exit:
David Hendrickscebee892015-05-23 20:30:30 -0700416 return ret;
417}
David Hendricks85f61c52015-06-04 19:52:59 -0700418
419/*
420 * Write-protect functions.
421 */
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700422static int mtd_wp_list_ranges(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700423{
424 /* TODO: implement this */
425 msg_perr("--wp-list is not currently implemented for MTD.\n");
426 return 1;
427}
428
429/*
430 * We only have MEMLOCK to enable write-protection for a particular block,
431 * so we need to do force the user to use --wp-range and --wp-enable
432 * command-line arguments simultaneously. (Fortunately, CrOS factory
433 * installer does this already).
434 *
435 * The --wp-range argument is processed first and will set these variables
436 * which --wp-enable will use afterward.
437 */
438static unsigned int wp_range_start;
439static unsigned int wp_range_len;
440static int wp_set_range_called = 0;
441
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700442static int mtd_wp_set_range(const struct flashctx *flash,
David Hendricks85f61c52015-06-04 19:52:59 -0700443 unsigned int start, unsigned int len)
444{
445 wp_range_start = start;
446 wp_range_len = len;
447
448 wp_set_range_called = 1;
449 return 0;
450}
451
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700452static int mtd_wp_enable_writeprotect(const struct flashctx *flash, enum wp_mode mode)
David Hendricks85f61c52015-06-04 19:52:59 -0700453{
454 struct erase_info_user entire_chip = {
455 .start = 0,
456 .length = mtd_total_size,
457 };
458 struct erase_info_user desired_range = {
459 .start = wp_range_start,
460 .length = wp_range_len,
461 };
462
463 if (!wp_set_range_called) {
464 msg_perr("For MTD, --wp-range and --wp-enable must be "
465 "used simultaneously.\n");
466 return 1;
467 }
468
469 /*
470 * MTD handles write-protection additively, so whatever new range is
471 * specified is added to the range which is currently protected. To be
472 * consistent with flashrom behavior with other programmer interfaces,
473 * we need to disable the current write protection and then enable
474 * it for the desired range.
475 */
476 if (ioctl(dev_fd, MEMUNLOCK, &entire_chip) == -1) {
477 msg_perr("%s: Failed to disable write-protection, ioctl: %s\n",
478 __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700479 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700480 return 1;
481 }
482
483 if (ioctl(dev_fd, MEMLOCK, &desired_range) == -1) {
484 msg_perr("%s: Failed to enable write-protection, ioctl: %s\n",
485 __func__, strerror(errno));
486 return 1;
487 }
488
489 return 0;
490}
491
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700492static int mtd_wp_disable_writeprotect(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700493{
494 struct erase_info_user erase_info;
495
496 if (wp_set_range_called) {
497 erase_info.start = wp_range_start;
498 erase_info.length = wp_range_len;
499 } else {
500 erase_info.start = 0;
501 erase_info.length = mtd_total_size;
502 }
503
504 if (ioctl(dev_fd, MEMUNLOCK, &erase_info) == -1) {
505 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
Brian Norris4df096b2016-07-27 18:36:38 -0700506 msg_perr("Did you disable WP#?\n");
David Hendricks85f61c52015-06-04 19:52:59 -0700507 return 1;
508 }
509
510 return 0;
511}
512
Souvik Ghoshd75cd672016-06-17 14:21:39 -0700513static int mtd_wp_status(const struct flashctx *flash)
David Hendricks85f61c52015-06-04 19:52:59 -0700514{
Brian Norris3ce4c472016-06-02 16:40:14 -0700515 uint32_t start = 0, len = 0;
516 int start_found = 0;
David Hendricks85f61c52015-06-04 19:52:59 -0700517 unsigned int u;
518
519 /* For now, assume only one contiguous region can be locked (NOR) */
520 /* FIXME: use flash struct members instead of raw MTD values here */
Wei-Ning Huang699269b2016-03-02 22:08:05 +0800521 for (u = 0; u < mtd_total_size; u += mtd_erasesize) {
David Hendricks85f61c52015-06-04 19:52:59 -0700522 int rc;
523 struct erase_info_user erase_info = {
524 .start = u,
525 .length = mtd_erasesize,
526 };
527
528 rc = ioctl(dev_fd, MEMISLOCKED, &erase_info);
529 if (rc < 0) {
530 msg_perr("%s: ioctl: %s\n", __func__, strerror(errno));
531 return 1;
532 } else if (rc == 1) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800533 if (!start_found) {
David Hendricks85f61c52015-06-04 19:52:59 -0700534 start = erase_info.start;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800535 start_found = 1;
536 }
Brian Norris3ce4c472016-06-02 16:40:14 -0700537 len += mtd_erasesize;
David Hendricks85f61c52015-06-04 19:52:59 -0700538 } else if (rc == 0) {
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800539 if (start_found) {
Brian Norris3ce4c472016-06-02 16:40:14 -0700540 /* TODO: changes required for supporting non-contiguous locked regions */
541 break;
Wei-Ning Huangbf285ee2016-03-04 11:36:14 +0800542 }
David Hendricks85f61c52015-06-04 19:52:59 -0700543 }
544
David Hendricks85f61c52015-06-04 19:52:59 -0700545 }
546
Wei-Ning Huangca907072016-04-27 11:30:17 +0800547 msg_cinfo("WP: write protect is %s.\n",
Brian Norris3ce4c472016-06-02 16:40:14 -0700548 start_found ? "enabled": "disabled");
Wei-Ning Huangca907072016-04-27 11:30:17 +0800549 msg_pinfo("WP: write protect range: start=0x%08x, "
Brian Norris3ce4c472016-06-02 16:40:14 -0700550 "len=0x%08x\n", start, len);
Wei-Ning Huangca907072016-04-27 11:30:17 +0800551
David Hendricks85f61c52015-06-04 19:52:59 -0700552 return 0;
553}
554
555static struct wp wp_mtd = {
556 .list_ranges = mtd_wp_list_ranges,
557 .set_range = mtd_wp_set_range,
558 .enable = mtd_wp_enable_writeprotect,
559 .disable = mtd_wp_disable_writeprotect,
560 .wp_status = mtd_wp_status,
561};