Edward O'Callaghan has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Add writeprotect support
[NOT-FOR-MERGE-(yet)]: PoC WIP.
BUG=b:153800563 BRANCH=none TEST=builds
Change-Id: Id93b5a1cb2da476fa8a7dde41d7b963024117474 Signed-off-by: Edward O'Callaghan quasisec@google.com --- M Makefile M cli_classic.c M flash.h M meson.build A writeprotect.c A writeprotect.h 6 files changed, 2,722 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/25/40325/1
diff --git a/Makefile b/Makefile index a533acf..a1bf898 100644 --- a/Makefile +++ b/Makefile @@ -589,7 +589,7 @@ CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \ sst28sf040.o 82802ab.o \ sst49lfxxxc.o sst_fwhub.o edi.o flashchips.o spi.o spi25.o spi25_statusreg.o \ - spi95.o opaque.o sfdp.o en29lv640b.o at45db.o + spi95.o opaque.o sfdp.o en29lv640b.o at45db.o writeprotect.o
############################################################################### # Library code. diff --git a/cli_classic.c b/cli_classic.c index 73cc417..9e2d8e8 100644 --- a/cli_classic.c +++ b/cli_classic.c @@ -17,6 +17,7 @@ * GNU General Public License for more details. */
+#include <errno.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> @@ -27,6 +28,7 @@ #include "flashchips.h" #include "fmap.h" #include "programmer.h" +#include "writeprotect.h" #include "libflashrom.h"
static void cli_classic_usage(const char *name) @@ -116,6 +118,8 @@ int list_supported_wiki = 0; #endif int flash_name = 0, flash_size = 0; + int set_wp_range = 0, set_wp_region = 0, set_wp_enable = 0, + set_wp_disable = 0, wp_status = 0, wp_list = 0; int read_it = 0, write_it = 0, erase_it = 0, verify_it = 0; int dont_verify_it = 0, dont_verify_all = 0, list_supported = 0, operation_specified = 0; struct flashrom_layout *layout = NULL; @@ -127,6 +131,12 @@ OPTION_FLASH_CONTENTS, OPTION_FLASH_NAME, OPTION_FLASH_SIZE, + OPTION_WP_STATUS, + OPTION_WP_SET_RANGE, + OPTION_WP_SET_REGION, + OPTION_WP_ENABLE, + OPTION_WP_DISABLE, + OPTION_WP_LIST, }; int ret = 0;
@@ -150,6 +160,12 @@ {"flash-name", 0, NULL, OPTION_FLASH_NAME}, {"flash-size", 0, NULL, OPTION_FLASH_SIZE}, {"get-size", 0, NULL, OPTION_FLASH_SIZE}, // (deprecated): back compatibility. + {"wp-status", 0, 0, OPTION_WP_STATUS}, + {"wp-range", 0, 0, OPTION_WP_SET_RANGE}, + {"wp-region", 1, 0, OPTION_WP_SET_REGION}, + {"wp-enable", optional_argument, 0, OPTION_WP_ENABLE}, + {"wp-disable", 0, 0, OPTION_WP_DISABLE}, + {"wp-list", 0, 0, OPTION_WP_LIST}, {"list-supported", 0, NULL, 'L'}, {"list-supported-wiki", 0, NULL, 'z'}, {"programmer", 1, NULL, 'p'}, @@ -169,6 +185,7 @@ char *tempstr = NULL; char *pparam = NULL; struct layout_include_args *include_args = NULL; + char *wp_mode_opt = NULL;
flashrom_set_log_callback((flashrom_log_callback *)&flashrom_print_cb);
@@ -283,6 +300,23 @@ cli_classic_validate_singleop(&operation_specified); flash_size = 1; break; + case OPTION_WP_STATUS: + wp_status = 1; + break; + case OPTION_WP_LIST: + wp_list = 1; + break; + case OPTION_WP_SET_RANGE: + set_wp_range = 1; + break; + case OPTION_WP_ENABLE: + set_wp_enable = 1; + if (optarg) + wp_mode_opt = strdup(optarg); + break; + case OPTION_WP_DISABLE: + set_wp_disable = 1; + break; case 'L': cli_classic_validate_singleop(&operation_specified); list_supported = 1; @@ -562,11 +596,19 @@ goto out_shutdown; }
- if (!(read_it | write_it | verify_it | erase_it | flash_name | flash_size)) { + if (!(read_it | write_it | verify_it | erase_it | flash_name | flash_size + | set_wp_range | set_wp_region | set_wp_enable | + set_wp_disable | wp_status | wp_list )) { msg_ginfo("No operations were specified.\n"); goto out_shutdown; }
+ if (set_wp_enable && set_wp_disable) { + msg_ginfo("Error: --wp-enable and --wp-disable are mutually exclusive\n"); + ret = 1; + goto out_shutdown; + } + if (flash_name) { if (fill_flash->chip->vendor && fill_flash->chip->name) { printf("vendor="%s" name="%s"\n", @@ -583,6 +625,110 @@ goto out_shutdown; }
+ if (wp_status) { + if (fill_flash->chip->wp && fill_flash->chip->wp->wp_status) { + ret |= fill_flash->chip->wp->wp_status(fill_flash); + } else { + msg_gerr("Error: write protect is not supported " + "on this flash chip.\n"); + ret = 1; + } + goto out_shutdown; + } + + /* Note: set_wp_disable should be done before setting the range */ + if (set_wp_disable) { + if (fill_flash->chip->wp && fill_flash->chip->wp->disable) { + ret |= fill_flash->chip->wp->disable(fill_flash); + } else { + msg_gerr("Error: write protect is not supported " + "on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + + if (!ret && set_wp_enable) { + enum wp_mode wp_mode; + + if (wp_mode_opt) + wp_mode = get_wp_mode(wp_mode_opt); + else + wp_mode = WP_MODE_HARDWARE; /* default */ + + if (wp_mode == WP_MODE_UNKNOWN) { + msg_gerr("Error: Invalid WP mode: "%s"\n", wp_mode_opt); + ret = 1; + goto out_shutdown; + } + + if (fill_flash->chip->wp && fill_flash->chip->wp->enable) { + ret |= fill_flash->chip->wp->enable(fill_flash, wp_mode); + } else { + msg_gerr("Error: write protect is not supported " + "on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + + if (wp_list) { + msg_ginfo("Valid write protection ranges:\n"); + if (fill_flash->chip->wp && fill_flash->chip->wp->list_ranges) { + ret |= fill_flash->chip->wp->list_ranges(fill_flash); + } else { + msg_gerr("Error: write protect is not supported " + "on this flash chip.\n"); + ret = 1; + } + goto out_shutdown; + } + + if (set_wp_range || set_wp_region) { + if (set_wp_range && set_wp_region) { + msg_gerr("Error: Cannot use both --wp-range and " + "--wp-region simultaneously.\n"); + ret = 1; + goto out_shutdown; + } + + if (!fill_flash->chip->wp || !fill_flash->chip->wp->set_range) { + msg_gerr("Error: write protect is not supported " + "on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + + /* Note: set_wp_range must happen before set_wp_enable */ + if (set_wp_range) { + unsigned int start, len; + char *endptr = NULL; + + if ((argc - optind) != 2) { + msg_gerr("Error: invalid number of arguments\n"); + ret = 1; + goto out_shutdown; + } + + /* FIXME: add some error checking */ + start = strtoul(argv[optind], &endptr, 0); + if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { + msg_gerr("Error: value "%s" invalid\n", argv[optind]); + ret = 1; + goto out_shutdown; + } + + len = strtoul(argv[optind + 1], &endptr, 0); + if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { + msg_gerr("Error: value "%s" invalid\n", argv[optind + 1]); + ret = 1; + goto out_shutdown; + } + + ret |= fill_flash->chip->wp->set_range(fill_flash, start, len); + } + if (layoutfile) { layout = get_global_layout(); } else if (ifd && (flashrom_layout_read_from_ifd(&layout, fill_flash, NULL, 0) || diff --git a/flash.h b/flash.h index 2f0143b..fefca9d 100644 --- a/flash.h +++ b/flash.h @@ -235,6 +235,8 @@ int (*unlock) (struct flashctx *flash); int (*write) (struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int (*read) (struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len); + uint8_t (*read_status) (const struct flashctx *flash); + int (*write_status) (const struct flashctx *flash, int status); struct voltage { uint16_t min; uint16_t max; @@ -243,6 +245,8 @@
/* SPI specific options (TODO: Make it a union in case other bustypes get specific options.) */ uint8_t wrea_override; /**< override opcode for write extended address register */ + + struct wp *wp; };
struct flashrom_flashctx { diff --git a/meson.build b/meson.build index 699370a..38bf671 100644 --- a/meson.build +++ b/meson.build @@ -350,6 +350,7 @@ srcs += 'udelay.c' srcs += 'w29ee011.c' srcs += 'w39.c' +srcs += 'writeprotect.c'
mapfile = 'libflashrom.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) diff --git a/writeprotect.c b/writeprotect.c new file mode 100644 index 0000000..f54ccad --- /dev/null +++ b/writeprotect.c @@ -0,0 +1,2512 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <strings.h> + +#include "flash.h" +#include "flashchips.h" +#include "chipdrivers.h" +#include "spi.h" +#include "writeprotect.h" + +/* + * The following procedures rely on look-up tables to match the user-specified + * range with the chip's supported ranges. This turned out to be the most + * elegant approach since diferent flash chips use different levels of + * granularity and methods to determine protected ranges. In other words, + * be stupid and simple since clever arithmetic will not work for many chips. + */ + +struct wp_range { + unsigned int start; /* starting address */ + unsigned int len; /* len */ +}; + +enum bit_state { + OFF = 0, + ON = 1, + X = -1 /* don't care. Must be bigger than max # of bp. */ +}; + +/* + * Generic write-protection schema for 25-series SPI flash chips. This assumes + * there is a status register that contains one or more consecutive bits which + * determine which address range is protected. + */ + +struct status_register_layout { + int bp0_pos; /* position of BP0 */ + int bp_bits; /* number of block protect bits */ + int srp_pos; /* position of status register protect enable bit */ +}; + +/* + * The following ranges and functions are useful for representing the + * writeprotect schema in which there are typically 5 bits of + * relevant information stored in status register 1: + * m.sec: This bit indicates the units (sectors vs. blocks) + * m.tb: The top-bottom bit indicates if the affected range is at the top of + * the flash memory's address space or at the bottom. + * bp: Bitmask representing the number of affected sectors/blocks. + */ +struct wp_range_descriptor { + struct modifier_bits m; + unsigned int bp; /* block protect bitfield */ + struct wp_range range; +}; + +struct wp_context { + struct status_register_layout sr1; /* status register 1 */ + struct wp_range_descriptor *descrs; + + /* + * Some chips store modifier bits in one or more special control + * registers instead of the status register like many older SPI NOR + * flash chips did. get_modifier_bits() and set_modifier_bits() will do + * any chip-specific operations necessary to get/set these bit values. + */ + int (*get_modifier_bits)(const struct flashctx *flash, + struct modifier_bits *m); + int (*set_modifier_bits)(const struct flashctx *flash, + struct modifier_bits *m); +}; + +struct w25q_status { + /* this maps to register layout -- do not change ordering */ + unsigned char busy : 1; + unsigned char wel : 1; + unsigned char bp0 : 1; + unsigned char bp1 : 1; + unsigned char bp2 : 1; + unsigned char tb : 1; + unsigned char sec : 1; + unsigned char srp0 : 1; +} __attribute__ ((packed)); + +/* Status register for large flash layouts with 4 BP bits */ +struct w25q_status_large { + unsigned char busy : 1; + unsigned char wel : 1; + unsigned char bp0 : 1; + unsigned char bp1 : 1; + unsigned char bp2 : 1; + unsigned char bp3 : 1; + unsigned char tb : 1; + unsigned char srp0 : 1; +} __attribute__ ((packed)); + +struct w25q_status_2 { + unsigned char srp1 : 1; + unsigned char qe : 1; + unsigned char rsvd : 6; +} __attribute__ ((packed)); + +int w25_range_to_status(const struct flashctx *flash, + unsigned int start, unsigned int len, + struct w25q_status *status); +int w25_status_to_range(const struct flashctx *flash, + const struct w25q_status *status, + unsigned int *start, unsigned int *len); + +/* + * Mask to extract write-protect enable and range bits + * Status register 1: + * SRP0: bit 7 + * range(BP2-BP0): bit 4-2 + * range(BP3-BP0): bit 5-2 (large chips) + * Status register 2: + * SRP1: bit 1 + */ +#define MASK_WP_AREA (0x9C) +#define MASK_WP_AREA_LARGE (0x9C) +#define MASK_WP2_AREA (0x01) + +struct wp_range_descriptor en25f40_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 504 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 496 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 480 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x000000, 448 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x000000, 384 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x000000, 512 * 1024} }, +}; + +struct wp_range_descriptor en25q40_ranges[] = { + { .m = { .sec = 0, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 504 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 496 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 480 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x0, {0x000000, 448 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 384 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 512 * 1024} }, +}; + +struct wp_range_descriptor en25q80_ranges[] = { + { .m = { .sec = 0, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 1016 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 1008 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 992 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x000000, 960 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x000000, 896 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x000000, 768 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x000000, 1024 * 1024} }, +}; + +struct wp_range_descriptor en25q32_ranges[] = { + { .m = { .sec = 0, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 4032 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 3968 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 3840 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x000000, 3584 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x000000, 3072 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x000000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x000000, 4096 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x010000, 4032 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x020000, 3968 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x040000, 3840 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x080000, 3584 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x100000, 3072 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x200000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 4096 * 1024} }, +}; + +struct wp_range_descriptor en25q64_ranges[] = { + { .m = { .sec = 0, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 8128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 8064 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 7936 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x000000, 7680 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x000000, 7168 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x000000, 6144 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x000000, 8192 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x010000, 8128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x020000, 8064 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x040000, 7936 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x080000, 7680 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x100000, 7168 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x200000, 6144 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 8192 * 1024} }, +}; + +struct wp_range_descriptor en25q128_ranges[] = { + { .m = { .sec = 0, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 16320 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 16256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 16128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x000000, 15872 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x000000, 15360 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x000000, 14336 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x000000, 16384 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x010000, 16320 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x020000, 16256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x040000, 16128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x080000, 15872 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x100000, 15360 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x200000, 14336 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 16384 * 1024} }, +}; + +struct wp_range_descriptor en25s64_ranges[] = { + { .m = { .sec = 0, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 8064 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 7936 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 7680 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x000000, 7168 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x000000, 6144 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x000000, 4096 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x000000, 8192 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x7e0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x7c0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x780000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x700000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x600000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x400000, 4096 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 8192 * 1024} }, +}; + +/* mx25l1005 ranges also work for the mx25l1005c */ +static struct wp_range_descriptor mx25l1005_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = X, .tb = X }, 0x1, {0x010000, 64 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x3, {0x000000, 128 * 1024} }, +}; + +static struct wp_range_descriptor mx25l2005_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = X, .tb = X }, 0x1, {0x030000, 64 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x2, {0x020000, 128 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x3, {0x000000, 256 * 1024} }, +}; + +static struct wp_range_descriptor mx25l4005_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = X, .tb = X }, 0x1, {0x070000, 64 * 1 * 1024} }, /* block 7 */ + { .m = { .sec = X, .tb = X }, 0x2, {0x060000, 64 * 2 * 1024} }, /* blocks 6-7 */ + { .m = { .sec = X, .tb = X }, 0x3, {0x040000, 64 * 4 * 1024} }, /* blocks 4-7 */ + { .m = { .sec = X, .tb = X }, 0x4, {0x000000, 512 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x5, {0x000000, 512 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x6, {0x000000, 512 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 512 * 1024} }, +}; + +static struct wp_range_descriptor mx25l8005_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = X, .tb = X }, 0x1, {0x0f0000, 64 * 1 * 1024} }, /* block 15 */ + { .m = { .sec = X, .tb = X }, 0x2, {0x0e0000, 64 * 2 * 1024} }, /* blocks 14-15 */ + { .m = { .sec = X, .tb = X }, 0x3, {0x0c0000, 64 * 4 * 1024} }, /* blocks 12-15 */ + { .m = { .sec = X, .tb = X }, 0x4, {0x080000, 64 * 8 * 1024} }, /* blocks 8-15 */ + { .m = { .sec = X, .tb = X }, 0x5, {0x000000, 1024 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x6, {0x000000, 1024 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 1024 * 1024} }, +}; + +static struct wp_range_descriptor mx25l1605d_ranges[] = { + { .m = { .sec = X, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = X, .tb = 0 }, 0x1, {0x1f0000, 64 * 1 * 1024} }, /* block 31 */ + { .m = { .sec = X, .tb = 0 }, 0x2, {0x1e0000, 64 * 2 * 1024} }, /* blocks 30-31 */ + { .m = { .sec = X, .tb = 0 }, 0x3, {0x1c0000, 64 * 4 * 1024} }, /* blocks 28-31 */ + { .m = { .sec = X, .tb = 0 }, 0x4, {0x180000, 64 * 8 * 1024} }, /* blocks 24-31 */ + { .m = { .sec = X, .tb = 0 }, 0x5, {0x100000, 64 * 16 * 1024} }, /* blocks 16-31 */ + { .m = { .sec = X, .tb = 0 }, 0x6, {0x000000, 64 * 32 * 1024} }, /* blocks 0-31 */ + { .m = { .sec = X, .tb = 0 }, 0x7, {0x000000, 64 * 32 * 1024} }, /* blocks 0-31 */ + + { .m = { .sec = X, .tb = 1 }, 0x0, {0x000000, 2048 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x1, {0x000000, 2048 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x2, {0x000000, 64 * 16 * 1024} }, /* blocks 0-15 */ + { .m = { .sec = X, .tb = 1 }, 0x3, {0x000000, 64 * 24 * 1024} }, /* blocks 0-23 */ + { .m = { .sec = X, .tb = 1 }, 0x4, {0x000000, 64 * 28 * 1024} }, /* blocks 0-27 */ + { .m = { .sec = X, .tb = 1 }, 0x5, {0x000000, 64 * 30 * 1024} }, /* blocks 0-29 */ + { .m = { .sec = X, .tb = 1 }, 0x6, {0x000000, 64 * 31 * 1024} }, /* blocks 0-30 */ + { .m = { .sec = X, .tb = 1 }, 0x7, {0x000000, 64 * 32 * 1024} }, /* blocks 0-31 */ +}; + +/* FIXME: Is there an mx25l3205 (without a trailing letter)? */ +static struct wp_range_descriptor mx25l3205d_ranges[] = { + { .m = { .sec = X, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = X, .tb = 0 }, 0x1, {0x3f0000, 64 * 1024} }, + { .m = { .sec = X, .tb = 0 }, 0x2, {0x3e0000, 128 * 1024} }, + { .m = { .sec = X, .tb = 0 }, 0x3, {0x3c0000, 256 * 1024} }, + { .m = { .sec = X, .tb = 0 }, 0x4, {0x380000, 512 * 1024} }, + { .m = { .sec = X, .tb = 0 }, 0x5, {0x300000, 1024 * 1024} }, + { .m = { .sec = X, .tb = 0 }, 0x6, {0x200000, 2048 * 1024} }, + { .m = { .sec = X, .tb = 0 }, 0x7, {0x000000, 4096 * 1024} }, + + { .m = { .sec = X, .tb = 1 }, 0x0, {0x000000, 4096 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x1, {0x000000, 2048 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x2, {0x000000, 3072 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x3, {0x000000, 3584 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x4, {0x000000, 3840 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x5, {0x000000, 3968 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x6, {0x000000, 4032 * 1024} }, + { .m = { .sec = X, .tb = 1 }, 0x7, {0x000000, 4096 * 1024} }, +}; + +static struct wp_range_descriptor mx25u3235e_ranges[] = { + { .m = { .sec = X, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x3f0000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x3e0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x3c0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x380000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x300000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x200000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x000000, 4096 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x0, {0x000000, 4096 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 3072 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 3584 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 3840 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 3968 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 4032 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 4096 * 1024} }, +}; + +static struct wp_range_descriptor mx25u6435e_ranges[] = { + { .m = { .sec = X, .tb = 0 }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x7f0000, 1 * 64 * 1024} }, /* block 127 */ + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x7e0000, 2 * 64 * 1024} }, /* blocks 126-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x7c0000, 4 * 64 * 1024} }, /* blocks 124-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x780000, 8 * 64 * 1024} }, /* blocks 120-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x700000, 16 * 64 * 1024} }, /* blocks 112-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x600000, 32 * 64 * 1024} }, /* blocks 96-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x400000, 64 * 64 * 1024} }, /* blocks 64-127 */ + + { .m = { .sec = 0, .tb = 1 }, 0x0, {0x000000, 64 * 64 * 1024} }, /* blocks 0-63 */ + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 96 * 64 * 1024} }, /* blocks 0-95 */ + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 112 * 64 * 1024} }, /* blocks 0-111 */ + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 120 * 64 * 1024} }, /* blocks 0-119 */ + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 124 * 64 * 1024} }, /* blocks 0-123 */ + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 126 * 64 * 1024} }, /* blocks 0-125 */ + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 127 * 64 * 1024} }, /* blocks 0-126 */ + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 128 * 64 * 1024} }, /* blocks 0-127 */ +}; + +#define MX25U12835E_TB (1 << 3) +static struct wp_range_descriptor mx25u12835e_tb0_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0xff0000, 1 * 64 * 1024} }, /* block 255 */ + { .m = { .sec = 0, .tb = 0 }, 0x2, {0xfe0000, 2 * 64 * 1024} }, /* blocks 254-255 */ + { .m = { .sec = 0, .tb = 0 }, 0x3, {0xfc0000, 4 * 64 * 1024} }, /* blocks 252-255 */ + { .m = { .sec = 0, .tb = 0 }, 0x4, {0xf80000, 8 * 64 * 1024} }, /* blocks 248-255 */ + { .m = { .sec = 0, .tb = 0 }, 0x5, {0xf00000, 16 * 64 * 1024} }, /* blocks 240-255 */ + { .m = { .sec = 0, .tb = 0 }, 0x6, {0xe00000, 32 * 64 * 1024} }, /* blocks 224-255 */ + { .m = { .sec = 0, .tb = 0 }, 0x7, {0xc00000, 64 * 64 * 1024} }, /* blocks 192-255 */ + { .m = { .sec = 0, .tb = 0 }, 0x8, {0x800000, 128 * 64 * 1024} }, /* blocks 128-255 */ + { .m = { .sec = 0, .tb = 0 }, 0x9, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 0 }, 0xa, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 0 }, 0xb, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 0 }, 0xc, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 0 }, 0xd, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 0 }, 0xe, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 0 }, 0xf, {0x000000, 256 * 64 * 1024} }, /* blocks all */ +}; + +static struct wp_range_descriptor mx25u12835e_tb1_ranges[] = { + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 1 * 64 * 1024} }, /* block 0 */ + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 2 * 64 * 1024} }, /* blocks 0-1 */ + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 4 * 64 * 1024} }, /* blocks 0-3 */ + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 8 * 64 * 1024} }, /* blocks 0-7 */ + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 16 * 64 * 1024} }, /* blocks 0-15 */ + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 32 * 64 * 1024} }, /* blocks 0-31 */ + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 64 * 64 * 1024} }, /* blocks 0-63 */ + { .m = { .sec = 0, .tb = 1 }, 0x8, {0x000000, 128 * 64 * 1024} }, /* blocks 0-127 */ + { .m = { .sec = 0, .tb = 1 }, 0x9, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 1 }, 0xa, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 1 }, 0xb, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 1 }, 0xc, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 1 }, 0xd, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 1 }, 0xe, {0x000000, 256 * 64 * 1024} }, /* blocks all */ + { .m = { .sec = 0, .tb = 1 }, 0xf, {0x000000, 256 * 64 * 1024} }, /* blocks all */ +}; + +static struct wp_range_descriptor n25q064_ranges[] = { + /* + * Note: For N25Q064, sec (usually in bit position 6) is called BP3 + * (block protect bit 3). It is only useful when all blocks are to + * be write-protected. + */ + { .m = { .sec = 0, .tb = 0 }, 0, {0, 0} }, /* none */ + + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x7f0000, 64 * 1024} }, /* block 127 */ + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x7e0000, 2 * 64 * 1024} }, /* blocks 126-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x7c0000, 4 * 64 * 1024} }, /* blocks 124-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x780000, 8 * 64 * 1024} }, /* blocks 120-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x700000, 16 * 64 * 1024} }, /* blocks 112-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x600000, 32 * 64 * 1024} }, /* blocks 96-127 */ + { .m = { .sec = 0, .tb = 0 }, 0x7, {0x400000, 64 * 64 * 1024} }, /* blocks 64-127 */ + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, /* block 0 */ + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 2 * 64 * 1024} }, /* blocks 0-1 */ + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 4 * 64 * 1024} }, /* blocks 0-3 */ + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 8 * 64 * 1024} }, /* blocks 0-7 */ + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 16 * 64 * 1024} }, /* blocks 0-15 */ + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 32 * 64 * 1024} }, /* blocks 0-31 */ + { .m = { .sec = 0, .tb = 1 }, 0x7, {0x000000, 64 * 64 * 1024} }, /* blocks 0-63 */ + + { .m = { .sec = X, .tb = 1 }, 0x0, {0x000000, 128 * 64 * 1024} }, /* all */ + { .m = { .sec = X, .tb = 1 }, 0x1, {0x000000, 128 * 64 * 1024} }, /* all */ + { .m = { .sec = X, .tb = 1 }, 0x2, {0x000000, 128 * 64 * 1024} }, /* all */ + { .m = { .sec = X, .tb = 1 }, 0x3, {0x000000, 128 * 64 * 1024} }, /* all */ + { .m = { .sec = X, .tb = 1 }, 0x4, {0x000000, 128 * 64 * 1024} }, /* all */ + { .m = { .sec = X, .tb = 1 }, 0x5, {0x000000, 128 * 64 * 1024} }, /* all */ + { .m = { .sec = X, .tb = 1 }, 0x6, {0x000000, 128 * 64 * 1024} }, /* all */ + { .m = { .sec = X, .tb = 1 }, 0x7, {0x000000, 128 * 64 * 1024} }, /* all */ +}; + +static struct wp_range_descriptor w25q16_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x1f0000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x1e0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x1c0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x180000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x100000, 1024 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 1024 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x6, {0x000000, 2048 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 2048 * 1024} }, + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x1ff000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x1fe000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x1fc000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x1f8000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x1f8000, 32 * 1024} }, + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x000000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x000000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x000000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x000000, 32 * 1024} }, +}; + +static struct wp_range_descriptor w25q32_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x3f0000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x3e0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x3c0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x380000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x300000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x200000, 2048 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 2048 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 4096 * 1024} }, + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x3ff000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x3fe000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x3fc000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x3f8000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x3f8000, 32 * 1024} }, + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x000000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x000000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x000000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x000000, 32 * 1024} }, +}; + +static struct wp_range_descriptor w25q80_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x0f0000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x0e0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x0c0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x080000, 512 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 512 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x6, {0x000000, 1024 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 1024 * 1024} }, + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x1ff000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x1fe000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x1fc000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x1f8000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x1f8000, 32 * 1024} }, + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x000000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x000000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x000000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x000000, 32 * 1024} }, +}; + +static struct wp_range_descriptor w25q64_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x7e0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x7c0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x780000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x700000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x600000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x400000, 4096 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 4096 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 8192 * 1024} }, + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x7ff000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x7fe000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x7fc000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x7f8000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x7f8000, 32 * 1024} }, + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x000000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x000000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x000000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x000000, 32 * 1024} }, +}; + +static struct wp_range_descriptor w25rq128_cmp0_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* NONE */ + + { .m = { .sec = 0, .tb = 0 }, 0x1, {0xfc0000, 256 * 1024} }, /* Upper 1/64 */ + { .m = { .sec = 0, .tb = 0 }, 0x2, {0xf80000, 512 * 1024} }, /* Upper 1/32 */ + { .m = { .sec = 0, .tb = 0 }, 0x3, {0xf00000, 1024 * 1024} }, /* Upper 1/16 */ + { .m = { .sec = 0, .tb = 0 }, 0x4, {0xe00000, 2048 * 1024} }, /* Upper 1/8 */ + { .m = { .sec = 0, .tb = 0 }, 0x5, {0xc00000, 4096 * 1024} }, /* Upper 1/4 */ + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x800000, 8192 * 1024} }, /* Upper 1/2 */ + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 256 * 1024} }, /* Lower 1/64 */ + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 512 * 1024} }, /* Lower 1/32 */ + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 1024 * 1024} }, /* Lower 1/16 */ + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 2048 * 1024} }, /* Lower 1/8 */ + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 4096 * 1024} }, /* Lower 1/4 */ + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 8192 * 1024} }, /* Lower 1/2 */ + + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 16384 * 1024} }, /* ALL */ + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0xfff000, 4 * 1024} }, /* Upper 1/4096 */ + { .m = { .sec = 1, .tb = 0 }, 0x2, {0xffe000, 8 * 1024} }, /* Upper 1/2048 */ + { .m = { .sec = 1, .tb = 0 }, 0x3, {0xffc000, 16 * 1024} }, /* Upper 1/1024 */ + { .m = { .sec = 1, .tb = 0 }, 0x4, {0xff8000, 32 * 1024} }, /* Upper 1/512 */ + { .m = { .sec = 1, .tb = 0 }, 0x5, {0xff8000, 32 * 1024} }, /* Upper 1/512 */ + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x000000, 4 * 1024} }, /* Lower 1/4096 */ + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x000000, 8 * 1024} }, /* Lower 1/2048 */ + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x000000, 16 * 1024} }, /* Lower 1/1024 */ + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x000000, 32 * 1024} }, /* Lower 1/512 */ + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x000000, 32 * 1024} }, /* Lower 1/512 */ +}; + +static struct wp_range_descriptor w25rq128_cmp1_ranges[] = { + { .m = { .sec = X, .tb = X }, 0x0, {0x000000, 16 * 1024 * 1024} }, /* ALL */ + + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 16128 * 1024} }, /* Lower 63/64 */ + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 15872 * 1024} }, /* Lower 31/32 */ + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 15 * 1024 * 1024} }, /* Lower 15/16 */ + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x000000, 14 * 1024 * 1024} }, /* Lower 7/8 */ + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x000000, 12 * 1024 * 1024} }, /* Lower 3/4 */ + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x000000, 8 * 1024 * 1024} }, /* Lower 1/2 */ + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x040000, 16128 * 1024} }, /* Upper 63/64 */ + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x080000, 15872 * 1024} }, /* Upper 31/32 */ + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x100000, 15 * 1024 * 1024} }, /* Upper 15/16 */ + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x200000, 14 * 1024 * 1024} }, /* Upper 7/8 */ + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x400000, 12 * 1024 * 1024} }, /* Upper 3/4 */ + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x800000, 8 * 1024 * 1024} }, /* Upper 1/2 */ + + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 0} }, /* NONE */ + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x000000, 16380 * 1024} }, /* Lower 4095/4096 */ + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x000000, 16376 * 1024} }, /* Lower 2048/2048 */ + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x000000, 16368 * 1024} }, /* Lower 1023/1024 */ + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x000000, 16352 * 1024} }, /* Lower 511/512 */ + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x000000, 16352 * 1024} }, /* Lower 511/512 */ + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x001000, 16380 * 1024} }, /* Upper 4095/4096 */ + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x002000, 16376 * 1024} }, /* Upper 2047/2048 */ + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x004000, 16368 * 1024} }, /* Upper 1023/1024 */ + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x008000, 16352 * 1024} }, /* Upper 511/512 */ + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x008000, 16352 * 1024} }, /* Upper 511/512 */ +}; + +static struct wp_range_descriptor w25rq256_cmp0_ranges[] = { + { .m = { .sec = X, .tb = X }, 0x0, {0x0000000, 0x0000000} }, /* NONE */ + + { .m = { .sec = X, .tb = 0 }, 0x1, {0x1ff0000, 64 * 1 * 1024} }, /* Upper 1/512 */ + { .m = { .sec = X, .tb = 0 }, 0x2, {0x1fe0000, 64 * 2 * 1024} }, /* Upper 1/256 */ + { .m = { .sec = X, .tb = 0 }, 0x3, {0x1fc0000, 64 * 4 * 1024} }, /* Upper 1/128 */ + { .m = { .sec = X, .tb = 0 }, 0x4, {0x1f80000, 64 * 8 * 1024} }, /* Upper 1/64 */ + { .m = { .sec = X, .tb = 0 }, 0x5, {0x1f00000, 64 * 16 * 1024} }, /* Upper 1/32 */ + { .m = { .sec = X, .tb = 0 }, 0x6, {0x1e00000, 64 * 32 * 1024} }, /* Upper 1/16 */ + { .m = { .sec = X, .tb = 0 }, 0x7, {0x1c00000, 64 * 64 * 1024} }, /* Upper 1/8 */ + { .m = { .sec = X, .tb = 0 }, 0x8, {0x1800000, 64 * 128 * 1024} }, /* Upper 1/4 */ + { .m = { .sec = X, .tb = 0 }, 0x9, {0x1000000, 64 * 256 * 1024} }, /* Upper 1/2 */ + + { .m = { .sec = X, .tb = 1 }, 0x1, {0x0000000, 64 * 1 * 1024} }, /* Lower 1/512 */ + { .m = { .sec = X, .tb = 1 }, 0x2, {0x0000000, 64 * 2 * 1024} }, /* Lower 1/256 */ + { .m = { .sec = X, .tb = 1 }, 0x3, {0x0000000, 64 * 4 * 1024} }, /* Lower 1/128 */ + { .m = { .sec = X, .tb = 1 }, 0x4, {0x0000000, 64 * 8 * 1024} }, /* Lower 1/64 */ + { .m = { .sec = X, .tb = 1 }, 0x5, {0x0000000, 64 * 16 * 1024} }, /* Lower 1/32 */ + { .m = { .sec = X, .tb = 1 }, 0x6, {0x0000000, 64 * 32 * 1024} }, /* Lower 1/16 */ + { .m = { .sec = X, .tb = 1 }, 0x7, {0x0000000, 64 * 64 * 1024} }, /* Lower 1/8 */ + { .m = { .sec = X, .tb = 1 }, 0x8, {0x0000000, 64 * 128 * 1024} }, /* Lower 1/4 */ + { .m = { .sec = X, .tb = 1 }, 0x9, {0x0000000, 64 * 256 * 1024} }, /* Lower 1/2 */ + + { .m = { .sec = X, .tb = X }, 0xa, {0x0000000, 64 * 512 * 1024} }, /* ALL */ + { .m = { .sec = X, .tb = X }, 0xb, {0x0000000, 64 * 512 * 1024} }, /* ALL */ + { .m = { .sec = X, .tb = X }, 0xc, {0x0000000, 64 * 512 * 1024} }, /* ALL */ + { .m = { .sec = X, .tb = X }, 0xd, {0x0000000, 64 * 512 * 1024} }, /* ALL */ + { .m = { .sec = X, .tb = X }, 0xe, {0x0000000, 64 * 512 * 1024} }, /* ALL */ + { .m = { .sec = X, .tb = X }, 0xf, {0x0000000, 64 * 512 * 1024} }, /* ALL */ +}; + +static struct wp_range_descriptor w25rq256_cmp1_ranges[] = { + { .m = { .sec = X, .tb = X }, 0x0, {0x0000000, 64 * 512 * 1024} }, /* ALL */ + + { .m = { .sec = X, .tb = 0 }, 0x1, {0x0000000, 64 * 511 * 1024} }, /* Lower 511/512 */ + { .m = { .sec = X, .tb = 0 }, 0x2, {0x0000000, 64 * 510 * 1024} }, /* Lower 255/256 */ + { .m = { .sec = X, .tb = 0 }, 0x3, {0x0000000, 64 * 508 * 1024} }, /* Lower 127/128 */ + { .m = { .sec = X, .tb = 0 }, 0x4, {0x0000000, 64 * 504 * 1024} }, /* Lower 63/64 */ + { .m = { .sec = X, .tb = 0 }, 0x5, {0x0000000, 64 * 496 * 1024} }, /* Lower 31/32 */ + { .m = { .sec = X, .tb = 0 }, 0x6, {0x0000000, 64 * 480 * 1024} }, /* Lower 15/16 */ + { .m = { .sec = X, .tb = 0 }, 0x7, {0x0000000, 64 * 448 * 1024} }, /* Lower 7/8 */ + { .m = { .sec = X, .tb = 0 }, 0x8, {0x0000000, 64 * 384 * 1024} }, /* Lower 3/4 */ + { .m = { .sec = X, .tb = 0 }, 0x9, {0x0000000, 64 * 256 * 1024} }, /* Lower 1/2 */ + + { .m = { .sec = X, .tb = 1 }, 0x1, {0x0010000, 64 * 511 * 1024} }, /* Upper 511/512 */ + { .m = { .sec = X, .tb = 1 }, 0x2, {0x0020000, 64 * 510 * 1024} }, /* Upper 255/256 */ + { .m = { .sec = X, .tb = 1 }, 0x3, {0x0040000, 64 * 508 * 1024} }, /* Upper 127/128 */ + { .m = { .sec = X, .tb = 1 }, 0x4, {0x0080000, 64 * 504 * 1024} }, /* Upper 63/64 */ + { .m = { .sec = X, .tb = 1 }, 0x5, {0x0100000, 64 * 496 * 1024} }, /* Upper 31/32 */ + { .m = { .sec = X, .tb = 1 }, 0x6, {0x0200000, 64 * 480 * 1024} }, /* Upper 15/16 */ + { .m = { .sec = X, .tb = 1 }, 0x7, {0x0400000, 64 * 448 * 1024} }, /* Upper 7/8 */ + { .m = { .sec = X, .tb = 1 }, 0x8, {0x0800000, 64 * 384 * 1024} }, /* Upper 3/4 */ + { .m = { .sec = X, .tb = 1 }, 0x9, {0x1000000, 64 * 256 * 1024} }, /* Upper 1/2 */ + + { .m = { .sec = X, .tb = X }, 0xa, {0x0000000, 0x0000000} }, /* NONE */ + { .m = { .sec = X, .tb = X }, 0xb, {0x0000000, 0x0000000} }, /* NONE */ + { .m = { .sec = X, .tb = X }, 0xc, {0x0000000, 0x0000000} }, /* NONE */ + { .m = { .sec = X, .tb = X }, 0xd, {0x0000000, 0x0000000} }, /* NONE */ + { .m = { .sec = X, .tb = X }, 0xe, {0x0000000, 0x0000000} }, /* NONE */ + { .m = { .sec = X, .tb = X }, 0xf, {0x0000000, 0x0000000} }, /* NONE */ +}; + +struct wp_range_descriptor w25x10_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x010000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x3, {0x000000, 128 * 1024} }, +}; + +struct wp_range_descriptor w25x20_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x030000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x020000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x3, {0x000000, 256 * 1024} }, +}; + +struct wp_range_descriptor w25x40_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x070000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x060000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x040000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x4, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x5, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x6, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x7, {0x000000, 512 * 1024} }, +}; + +struct wp_range_descriptor w25x80_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x0F0000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x0E0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x0C0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x080000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x5, {0x000000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x6, {0x000000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x7, {0x000000, 1024 * 1024} }, +}; + +static struct wp_range_descriptor gd25q40_cmp0_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* None */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x070000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x060000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x040000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 64 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = X }, 0x4, {0x000000, 512 * 1024} }, /* All */ + { .m = { .sec = 0, .tb = X }, 0x5, {0x000000, 512 * 1024} }, /* All */ + { .m = { .sec = 0, .tb = X }, 0x6, {0x000000, 512 * 1024} }, /* All */ + { .m = { .sec = 0, .tb = X }, 0x7, {0x000000, 512 * 1024} }, /* All */ + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x07F000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x07E000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x07C000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x078000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x078000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x6, {0x078000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x000000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x000000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x000000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x6, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = X }, 0x7, {0x000000, 512 * 1024} }, /* All */ +}; + +static struct wp_range_descriptor gd25q40_cmp1_ranges[] = { + { .m = { .sec = X, .tb = X }, 0x0, {0x000000, 512 * 1024} }, /* ALL */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x000000, 448 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x000000, 384 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x000000, 256 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x010000, 448 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x020000, 384 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x040000, 256 * 1024} }, + + { .m = { .sec = 0, .tb = X }, 0x4, {0x000000, 0} }, /* None */ + { .m = { .sec = 0, .tb = X }, 0x5, {0x000000, 0} }, /* None */ + { .m = { .sec = 0, .tb = X }, 0x6, {0x000000, 0} }, /* None */ + { .m = { .sec = 0, .tb = X }, 0x7, {0x000000, 0} }, /* None */ + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x000000, 508 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x000000, 504 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x000000, 496 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x000000, 480 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x000000, 480 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x6, {0x000000, 480 * 1024} }, + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x001000, 508 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x002000, 504 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x004000, 496 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x008000, 480 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x008000, 480 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x6, {0x008000, 480 * 1024} }, + + { .m = { .sec = 1, .tb = X }, 0x7, {0x000000, 0} }, /* None */ +}; + +static struct wp_range_descriptor gd25q64_ranges[] = { + { .m = { .sec = X, .tb = X }, 0, {0, 0} }, /* none */ + { .m = { .sec = 0, .tb = 0 }, 0x1, {0x7e0000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x2, {0x7c0000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x3, {0x780000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x4, {0x700000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x5, {0x600000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 0 }, 0x6, {0x400000, 4096 * 1024} }, + + { .m = { .sec = 0, .tb = 1 }, 0x1, {0x000000, 128 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x2, {0x000000, 256 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x3, {0x000000, 512 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x4, {0x000000, 1024 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x5, {0x000000, 2048 * 1024} }, + { .m = { .sec = 0, .tb = 1 }, 0x6, {0x000000, 4096 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x000000, 8192 * 1024} }, + + { .m = { .sec = 1, .tb = 0 }, 0x1, {0x7ff000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x2, {0x7fe000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x3, {0x7fc000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x4, {0x7f8000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x5, {0x7f8000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 0 }, 0x6, {0x7f8000, 32 * 1024} }, + + { .m = { .sec = 1, .tb = 1 }, 0x1, {0x000000, 4 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x2, {0x000000, 8 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x3, {0x000000, 16 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x4, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x5, {0x000000, 32 * 1024} }, + { .m = { .sec = 1, .tb = 1 }, 0x6, {0x000000, 32 * 1024} }, +}; + +static struct wp_range_descriptor a25l040_ranges[] = { + { .m = { .sec = X, .tb = X }, 0x0, {0, 0} }, /* none */ + { .m = { .sec = X, .tb = X }, 0x1, {0x70000, 64 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x2, {0x60000, 128 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x3, {0x40000, 256 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x4, {0x00000, 512 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x5, {0x00000, 512 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x6, {0x00000, 512 * 1024} }, + { .m = { .sec = X, .tb = X }, 0x7, {0x00000, 512 * 1024} }, +}; + +static uint8_t do_read_status(const struct flashctx *flash) +{ + if (flash->chip->read_status) + return flash->chip->read_status(flash); + else + return spi_read_status_register(flash); +} + +static int do_write_status(const struct flashctx *flash, int status) +{ + if (flash->chip->write_status) + return flash->chip->write_status(flash, status); + else + return spi_write_status_register(flash, status); +} + +/* FIXME: Move to spi25.c if it's a JEDEC standard opcode */ +static uint8_t w25q_read_status_register_2(const struct flashctx *flash) +{ + static const unsigned char cmd[JEDEC_RDSR_OUTSIZE] = { 0x35 }; + unsigned char readarr[2]; + int ret; + + /* Read Status Register */ + ret = spi_send_command(flash, sizeof(cmd), sizeof(readarr), cmd, readarr); + if (ret) { + /* + * FIXME: make this a benign failure for now in case we are + * unable to execute the opcode + */ + msg_cdbg("RDSR2 failed!\n"); + readarr[0] = 0x00; + } + + return readarr[0]; +} + +/* FIXME: Move to spi25.c if it's a JEDEC standard opcode */ +uint8_t mx25l_read_config_register(const struct flashctx *flash);//XXX +uint8_t mx25l_read_config_register(const struct flashctx *flash) +{ + static const unsigned char cmd[JEDEC_RDSR_OUTSIZE] = { 0x15 }; + unsigned char readarr[2]; /* leave room for dummy byte */ + int ret; + + ret = spi_send_command(flash, sizeof(cmd), sizeof(readarr), cmd, readarr); + if (ret) { + msg_cdbg("RDCR failed!\n"); + readarr[0] = 0x00; + } + + return readarr[0]; +} + +/* Given a flash chip, this function returns its range table. */ +static int w25_range_table(const struct flashctx *flash, + struct wp_range_descriptor **descrs, + int *num_entries) +{ + uint8_t cr; + + *descrs = 0; + *num_entries = 0; + + switch (flash->chip->manufacture_id) { + case WINBOND_NEX_ID: + switch(flash->chip->model_id) { + case WINBOND_NEX_W25X10: + *descrs = w25x10_ranges; + *num_entries = ARRAY_SIZE(w25x10_ranges); + break; + case WINBOND_NEX_W25X20: + *descrs = w25x20_ranges; + *num_entries = ARRAY_SIZE(w25x20_ranges); + break; + case WINBOND_NEX_W25X40: + *descrs = w25x40_ranges; + *num_entries = ARRAY_SIZE(w25x40_ranges); + break; + case WINBOND_NEX_W25X80: + *descrs = w25x80_ranges; + *num_entries = ARRAY_SIZE(w25x80_ranges); + break; + case WINBOND_NEX_W25Q80_V: + *descrs = w25q80_ranges; + *num_entries = ARRAY_SIZE(w25q80_ranges); + break; + case WINBOND_NEX_W25Q16_V: + *descrs = w25q16_ranges; + *num_entries = ARRAY_SIZE(w25q16_ranges); + break; + case WINBOND_NEX_W25Q32_V: + case WINBOND_NEX_W25Q32_W: + //case WINBOND_NEX_W25Q32JW: + *descrs = w25q32_ranges; + *num_entries = ARRAY_SIZE(w25q32_ranges); + break; + case WINBOND_NEX_W25Q64_V: + case WINBOND_NEX_W25Q64_W: + *descrs = w25q64_ranges; + *num_entries = ARRAY_SIZE(w25q64_ranges); + break; + case WINBOND_NEX_W25Q128_DTR: + case WINBOND_NEX_W25Q128_V_M: + case WINBOND_NEX_W25Q128_V: + case WINBOND_NEX_W25Q128_W: + if (w25q_read_status_register_2(flash) & (1 << 6)) { + /* CMP == 1 */ + *descrs = w25rq128_cmp1_ranges; + *num_entries = ARRAY_SIZE(w25rq128_cmp1_ranges); + } else { + /* CMP == 0 */ + *descrs = w25rq128_cmp0_ranges; + *num_entries = ARRAY_SIZE(w25rq128_cmp0_ranges); + } + break; + case WINBOND_NEX_W25Q256JV_M: + if (w25q_read_status_register_2(flash) & (1 << 6)) { + /* CMP == 1 */ + *descrs = w25rq256_cmp1_ranges; + *num_entries = ARRAY_SIZE(w25rq256_cmp1_ranges); + } else { + /* CMP == 0 */ + *descrs = w25rq256_cmp0_ranges; + *num_entries = ARRAY_SIZE(w25rq256_cmp0_ranges); + } + break; + default: + msg_cerr("%s() %d: WINBOND flash chip mismatch (0x%04x)" + ", aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + case EON_ID_NOPREFIX: + switch (flash->chip->model_id) { + case EON_EN25F40: + *descrs = en25f40_ranges; + *num_entries = ARRAY_SIZE(en25f40_ranges); + break; + case EON_EN25Q40: + *descrs = en25q40_ranges; + *num_entries = ARRAY_SIZE(en25q40_ranges); + break; + case EON_EN25Q80: + *descrs = en25q80_ranges; + *num_entries = ARRAY_SIZE(en25q80_ranges); + break; + case EON_EN25Q32: + *descrs = en25q32_ranges; + *num_entries = ARRAY_SIZE(en25q32_ranges); + break; + case EON_EN25Q64: + *descrs = en25q64_ranges; + *num_entries = ARRAY_SIZE(en25q64_ranges); + break; + case EON_EN25Q128: + *descrs = en25q128_ranges; + *num_entries = ARRAY_SIZE(en25q128_ranges); + break; + case EON_EN25S64: + *descrs = en25s64_ranges; + *num_entries = ARRAY_SIZE(en25s64_ranges); + break; + default: + msg_cerr("%s():%d: EON flash chip mismatch (0x%04x)" + ", aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + case MACRONIX_ID: + switch (flash->chip->model_id) { + case MACRONIX_MX25L1005: + *descrs = mx25l1005_ranges; + *num_entries = ARRAY_SIZE(mx25l1005_ranges); + break; + case MACRONIX_MX25L2005: + *descrs = mx25l2005_ranges; + *num_entries = ARRAY_SIZE(mx25l2005_ranges); + break; + case MACRONIX_MX25L4005: + *descrs = mx25l4005_ranges; + *num_entries = ARRAY_SIZE(mx25l4005_ranges); + break; + case MACRONIX_MX25L8005: + *descrs = mx25l8005_ranges; + *num_entries = ARRAY_SIZE(mx25l8005_ranges); + break; + case MACRONIX_MX25L1605: + /* FIXME: MX25L1605 and MX25L1605D have different write + * protection capabilities, but share IDs */ + *descrs = mx25l1605d_ranges; + *num_entries = ARRAY_SIZE(mx25l1605d_ranges); + break; + case MACRONIX_MX25L3205: + *descrs = mx25l3205d_ranges; + *num_entries = ARRAY_SIZE(mx25l3205d_ranges); + break; + case MACRONIX_MX25U3235E: + *descrs = mx25u3235e_ranges; + *num_entries = ARRAY_SIZE(mx25u3235e_ranges); + break; + case MACRONIX_MX25U6435E: + *descrs = mx25u6435e_ranges; + *num_entries = ARRAY_SIZE(mx25u6435e_ranges); + break; + case MACRONIX_MX25U12835E: + cr = mx25l_read_config_register(flash); + if (cr & MX25U12835E_TB) { /* T/B == 1 */ + *descrs = mx25u12835e_tb1_ranges; + *num_entries = ARRAY_SIZE(mx25u12835e_tb1_ranges); + } else { /* T/B == 0 */ + *descrs = mx25u12835e_tb0_ranges; + *num_entries = ARRAY_SIZE(mx25u12835e_tb0_ranges); + } + break; + default: + msg_cerr("%s():%d: MXIC flash chip mismatch (0x%04x)" + ", aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + case ST_ID: + switch(flash->chip->model_id) { + case ST_N25Q064__1E: + case ST_N25Q064__3E: + *descrs = n25q064_ranges; + *num_entries = ARRAY_SIZE(n25q064_ranges); + break; + default: + msg_cerr("%s() %d: Micron flash chip mismatch" + " (0x%04x), aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + case GIGADEVICE_ID: + switch(flash->chip->model_id) { + case GIGADEVICE_GD25LQ32: + *descrs = w25q32_ranges; + *num_entries = ARRAY_SIZE(w25q32_ranges); + break; + case GIGADEVICE_GD25Q40: + if (w25q_read_status_register_2(flash) & (1 << 6)) { + /* CMP == 1 */ + *descrs = gd25q40_cmp1_ranges; + *num_entries = ARRAY_SIZE(gd25q40_cmp1_ranges); + } else { + *descrs = gd25q40_cmp0_ranges; + *num_entries = ARRAY_SIZE(gd25q40_cmp0_ranges); + } + break; + case GIGADEVICE_GD25Q64: + case GIGADEVICE_GD25LQ64: + *descrs = gd25q64_ranges; + *num_entries = ARRAY_SIZE(gd25q64_ranges); + break; + case GIGADEVICE_GD25Q128: + case GIGADEVICE_GD25LQ128CD: + if (w25q_read_status_register_2(flash) & (1 << 6)) { + /* CMP == 1 */ + *descrs = w25rq128_cmp1_ranges; + *num_entries = ARRAY_SIZE(w25rq128_cmp1_ranges); + } else { + /* CMP == 0 */ + *descrs = w25rq128_cmp0_ranges; + *num_entries = ARRAY_SIZE(w25rq128_cmp0_ranges); + } + break; + case GIGADEVICE_GD25Q256D: + *descrs = w25rq256_cmp0_ranges; + *num_entries = ARRAY_SIZE(w25rq256_cmp0_ranges); + break; + default: + msg_cerr("%s() %d: GigaDevice flash chip mismatch" + " (0x%04x), aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + case AMIC_ID_NOPREFIX: + switch(flash->chip->model_id) { + case AMIC_A25L040: + *descrs = a25l040_ranges; + *num_entries = ARRAY_SIZE(a25l040_ranges); + break; + default: + msg_cerr("%s() %d: AMIC flash chip mismatch" + " (0x%04x), aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + case ATMEL_ID: + switch(flash->chip->model_id) { + //case ATMEL_AT25SF128A: + case ATMEL_AT25SL128A: + if (w25q_read_status_register_2(flash) & (1 << 6)) { + /* CMP == 1 */ + *descrs = w25rq128_cmp1_ranges; + *num_entries = ARRAY_SIZE(w25rq128_cmp1_ranges); + } else { + /* CMP == 0 */ + *descrs = w25rq128_cmp0_ranges; + *num_entries = ARRAY_SIZE(w25rq128_cmp0_ranges); + } + break; + default: + msg_cerr("%s() %d: Atmel flash chip mismatch" + " (0x%04x), aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + default: + msg_cerr("%s: flash vendor (0x%x) not found, aborting\n", + __func__, flash->chip->manufacture_id); + return -1; + } + + return 0; +} + +int w25_range_to_status(const struct flashctx *flash, + unsigned int start, unsigned int len, + struct w25q_status *status) +{ + struct wp_range_descriptor *descrs; + int i, range_found = 0; + int num_entries; + + if (w25_range_table(flash, &descrs, &num_entries)) + return -1; + + for (i = 0; i < num_entries; i++) { + struct wp_range *r = &descrs[i].range; + + msg_cspew("comparing range 0x%x 0x%x / 0x%x 0x%x\n", + start, len, r->start, r->len); + if ((start == r->start) && (len == r->len)) { + status->bp0 = descrs[i].bp & 1; + status->bp1 = descrs[i].bp >> 1; + status->bp2 = descrs[i].bp >> 2; + status->tb = descrs[i].m.tb; + status->sec = descrs[i].m.sec; + + range_found = 1; + break; + } + } + + if (!range_found) { + msg_cerr("%s: matching range not found\n", __func__); + return -1; + } + + return 0; +} + +int w25_status_to_range(const struct flashctx *flash, + const struct w25q_status *status, + unsigned int *start, unsigned int *len) +{ + struct wp_range_descriptor *descrs; + int i, status_found = 0; + int num_entries; + + if (w25_range_table(flash, &descrs, &num_entries)) + return -1; + + for (i = 0; i < num_entries; i++) { + int bp; + int table_bp, table_tb, table_sec; + + bp = status->bp0 | (status->bp1 << 1) | (status->bp2 << 2); + msg_cspew("comparing 0x%x 0x%x / 0x%x 0x%x / 0x%x 0x%x\n", + bp, descrs[i].bp, + status->tb, descrs[i].m.tb, + status->sec, descrs[i].m.sec); + table_bp = descrs[i].bp; + table_tb = descrs[i].m.tb; + table_sec = descrs[i].m.sec; + if ((bp == table_bp || table_bp == X) && + (status->tb == table_tb || table_tb == X) && + (status->sec == table_sec || table_sec == X)) { + *start = descrs[i].range.start; + *len = descrs[i].range.len; + + status_found = 1; + break; + } + } + + if (!status_found) { + msg_cerr("matching status not found\n"); + return -1; + } + + return 0; +} + +/* Given a [start, len], this function calls w25_range_to_status() to convert + * it to flash-chip-specific range bits, then sets into status register. + */ +static int w25_set_range(const struct flashctx *flash, + unsigned int start, unsigned int len) +{ + struct w25q_status status; + int tmp = 0; + int expected = 0; + + memset(&status, 0, sizeof(status)); + tmp = do_read_status(flash); + memcpy(&status, &tmp, 1); + msg_cdbg("%s: old status: 0x%02x\n", __func__, tmp); + + if (w25_range_to_status(flash, start, len, &status)) + return -1; + + msg_cdbg("status.busy: %x\n", status.busy); + msg_cdbg("status.wel: %x\n", status.wel); + msg_cdbg("status.bp0: %x\n", status.bp0); + msg_cdbg("status.bp1: %x\n", status.bp1); + msg_cdbg("status.bp2: %x\n", status.bp2); + msg_cdbg("status.tb: %x\n", status.tb); + msg_cdbg("status.sec: %x\n", status.sec); + msg_cdbg("status.srp0: %x\n", status.srp0); + + memcpy(&expected, &status, sizeof(status)); + do_write_status(flash, expected); + + tmp = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, tmp); + if ((tmp & MASK_WP_AREA) != (expected & MASK_WP_AREA)) { + msg_cerr("expected=0x%02x, but actual=0x%02x.\n", + expected, tmp); + return 1; + } + + return 0; +} + +/* Print out the current status register value with human-readable text. */ +static int w25_wp_status(const struct flashctx *flash) +{ + struct w25q_status status; + int tmp; + unsigned int start, len; + int ret = 0; + + memset(&status, 0, sizeof(status)); + tmp = do_read_status(flash); + memcpy(&status, &tmp, 1); + msg_cinfo("WP: status: 0x%02x\n", tmp); + msg_cinfo("WP: status.srp0: %x\n", status.srp0); + msg_cinfo("WP: write protect is %s.\n", + status.srp0 ? "enabled" : "disabled"); + + msg_cinfo("WP: write protect range: "); + if (w25_status_to_range(flash, &status, &start, &len)) { + msg_cinfo("(cannot resolve the range)\n"); + ret = -1; + } else { + msg_cinfo("start=0x%08x, len=0x%08x\n", start, len); + } + + return ret; +} + +static int w25q_large_range_to_status(const struct flashctx *flash, + unsigned int start, unsigned int len, + struct w25q_status_large *status) +{ + struct wp_range_descriptor *descrs; + int i, range_found = 0; + int num_entries; + + if (w25_range_table(flash, &descrs, &num_entries)) + return -1; + + for (i = 0; i < num_entries; i++) { + struct wp_range *r = &descrs[i].range; + + msg_cspew("comparing range 0x%x 0x%x / 0x%x 0x%x\n", + start, len, r->start, r->len); + if ((start == r->start) && (len == r->len)) { + status->bp0 = descrs[i].bp & 1; + status->bp1 = descrs[i].bp >> 1; + status->bp2 = descrs[i].bp >> 2; + status->bp3 = descrs[i].bp >> 3; + /* + * For MX25U12835E chip, Top/Bottom (T/B) bit is not + * part of status register and in that bit position is + * Quad Enable (QE) + */ + if (flash->chip->manufacture_id != MACRONIX_ID || + flash->chip->model_id != MACRONIX_MX25U12835E) + status->tb = descrs[i].m.tb; + + range_found = 1; + break; + } + } + + if (!range_found) { + msg_cerr("%s: matching range not found\n", __func__); + return -1; + } + + return 0; +} + +static int w25_large_status_to_range(const struct flashctx *flash, + const struct w25q_status_large *status, + unsigned int *start, unsigned int *len) +{ + struct wp_range_descriptor *descrs; + int i, status_found = 0; + int num_entries; + + if (w25_range_table(flash, &descrs, &num_entries)) + return -1; + + for (i = 0; i < num_entries; i++) { + int bp; + int table_bp, table_tb; + + bp = status->bp0 | (status->bp1 << 1) | (status->bp2 << 2) | + (status->bp3 << 3); + msg_cspew("comparing 0x%x 0x%x / 0x%x 0x%x\n", + bp, descrs[i].bp, + status->tb, descrs[i].m.tb); + table_bp = descrs[i].bp; + table_tb = descrs[i].m.tb; + if ((bp == table_bp || table_bp == X) && + (status->tb == table_tb || table_tb == X)) { + *start = descrs[i].range.start; + *len = descrs[i].range.len; + + status_found = 1; + break; + } + } + + if (!status_found) { + msg_cerr("matching status not found\n"); + return -1; + } + + return 0; +} + +/* Given a [start, len], this function calls w25_range_to_status() to convert + * it to flash-chip-specific range bits, then sets into status register. + * Returns 0 if successful, -1 on error, and 1 if reading back was different. + */ +static int w25q_large_set_range(const struct flashctx *flash, + unsigned int start, unsigned int len) +{ + struct w25q_status_large status; + int tmp; + int expected = 0; + + memset(&status, 0, sizeof(status)); + tmp = do_read_status(flash); + memcpy(&status, &tmp, 1); + msg_cdbg("%s: old status: 0x%02x\n", __func__, tmp); + + if (w25q_large_range_to_status(flash, start, len, &status)) + return -1; + + msg_cdbg("status.busy: %x\n", status.busy); + msg_cdbg("status.wel: %x\n", status.wel); + msg_cdbg("status.bp0: %x\n", status.bp0); + msg_cdbg("status.bp1: %x\n", status.bp1); + msg_cdbg("status.bp2: %x\n", status.bp2); + msg_cdbg("status.bp3: %x\n", status.bp3); + msg_cdbg("status.tb: %x\n", status.tb); + msg_cdbg("status.srp0: %x\n", status.srp0); + + memcpy(&expected, &status, sizeof(status)); + do_write_status(flash, expected); + + tmp = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, tmp); + if ((tmp & MASK_WP_AREA_LARGE) != (expected & MASK_WP_AREA_LARGE)) { + msg_cerr("expected=0x%02x, but actual=0x%02x.\n", + expected, tmp); + return 1; + } + + return 0; +} + +static int w25q_large_wp_status(const struct flashctx *flash) +{ + struct w25q_status_large sr1; + struct w25q_status_2 sr2; + uint8_t tmp[2]; + unsigned int start, len; + int ret = 0; + + memset(&sr1, 0, sizeof(sr1)); + tmp[0] = do_read_status(flash); + memcpy(&sr1, &tmp[0], 1); + + memset(&sr2, 0, sizeof(sr2)); + tmp[1] = w25q_read_status_register_2(flash); + memcpy(&sr2, &tmp[1], 1); + + msg_cinfo("WP: status: 0x%02x%02x\n", tmp[1], tmp[0]); + msg_cinfo("WP: status.srp0: %x\n", sr1.srp0); + msg_cinfo("WP: status.srp1: %x\n", sr2.srp1); + msg_cinfo("WP: write protect is %s.\n", + (sr1.srp0 || sr2.srp1) ? "enabled" : "disabled"); + + msg_cinfo("WP: write protect range: "); + if (w25_large_status_to_range(flash, &sr1, &start, &len)) { + msg_cinfo("(cannot resolve the range)\n"); + ret = -1; + } else { + msg_cinfo("start=0x%08x, len=0x%08x\n", start, len); + } + + return ret; +} + +/* Set/clear the SRP0 bit in the status register. */ +static int w25_set_srp0(const struct flashctx *flash, int enable) +{ + struct w25q_status status; + int tmp = 0; + int expected = 0; + + memset(&status, 0, sizeof(status)); + tmp = do_read_status(flash); + /* FIXME: this is NOT endian-free copy. */ + memcpy(&status, &tmp, 1); + msg_cdbg("%s: old status: 0x%02x\n", __func__, tmp); + + status.srp0 = enable ? 1 : 0; + memcpy(&expected, &status, sizeof(status)); + do_write_status(flash, expected); + + tmp = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, tmp); + if ((tmp & MASK_WP_AREA) != (expected & MASK_WP_AREA)) + return 1; + + return 0; +} + +static int w25_enable_writeprotect(const struct flashctx *flash, + enum wp_mode wp_mode) +{ + int ret; + + if (wp_mode != WP_MODE_HARDWARE) { + msg_cerr("%s(): unsupported write-protect mode\n", __func__); + return 1; + } + + ret = w25_set_srp0(flash, 1); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + return ret; +} + +static int w25_disable_writeprotect(const struct flashctx *flash) +{ + int ret; + + ret = w25_set_srp0(flash, 0); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int w25_list_ranges(const struct flashctx *flash) +{ + struct wp_range_descriptor *descrs; + int i, num_entries; + + if (w25_range_table(flash, &descrs, &num_entries)) + return -1; + + for (i = 0; i < num_entries; i++) { + msg_cinfo("start: 0x%06x, length: 0x%06x\n", + descrs[i].range.start, + descrs[i].range.len); + } + + return 0; +} + +static int w25q_wp_status(const struct flashctx *flash) +{ + struct w25q_status sr1; + struct w25q_status_2 sr2; + uint8_t tmp[2]; + unsigned int start, len; + int ret = 0; + + memset(&sr1, 0, sizeof(sr1)); + tmp[0] = do_read_status(flash); + memcpy(&sr1, &tmp[0], 1); + + memset(&sr2, 0, sizeof(sr2)); + tmp[1] = w25q_read_status_register_2(flash); + memcpy(&sr2, &tmp[1], 1); + + msg_cinfo("WP: status: 0x%02x%02x\n", tmp[1], tmp[0]); + msg_cinfo("WP: status.srp0: %x\n", sr1.srp0); + msg_cinfo("WP: status.srp1: %x\n", sr2.srp1); + msg_cinfo("WP: write protect is %s.\n", + (sr1.srp0 || sr2.srp1) ? "enabled" : "disabled"); + + msg_cinfo("WP: write protect range: "); + if (w25_status_to_range(flash, &sr1, &start, &len)) { + msg_cinfo("(cannot resolve the range)\n"); + ret = -1; + } else { + msg_cinfo("start=0x%08x, len=0x%08x\n", start, len); + } + + return ret; +} + +/* + * W25Q adds an optional byte to the standard WRSR opcode. If /CS is + * de-asserted after the first byte, then it acts like a JEDEC-standard + * WRSR command. if /CS is asserted, then the next data byte is written + * into status register 2. + */ +#define W25Q_WRSR_OUTSIZE 0x03 +static int w25q_write_status_register_WREN(const struct flashctx *flash, uint8_t s1, uint8_t s2) +{ + int result; + struct spi_command cmds[] = { + { + /* FIXME: WRSR requires either EWSR or WREN depending on chip type. */ + .writecnt = JEDEC_WREN_OUTSIZE, + .writearr = (const unsigned char[]){ JEDEC_WREN }, + .readcnt = 0, + .readarr = NULL, + }, { + .writecnt = W25Q_WRSR_OUTSIZE, + .writearr = (const unsigned char[]){ JEDEC_WRSR, s1, s2 }, + .readcnt = 0, + .readarr = NULL, + }, { + .writecnt = 0, + .writearr = NULL, + .readcnt = 0, + .readarr = NULL, + }}; + + result = spi_send_multicommand(flash, cmds); + if (result) { + msg_cerr("%s failed during command execution\n", + __func__); + } + + /* WRSR performs a self-timed erase before the changes take effect. */ + programmer_delay(100 * 1000); + + return result; +} + +/* + * Set/clear the SRP1 bit in status register 2. + * FIXME: make this more generic if other chips use the same SR2 layout + */ +static int w25q_set_srp1(const struct flashctx *flash, int enable) +{ + struct w25q_status sr1; + struct w25q_status_2 sr2; + uint8_t tmp, expected; + + tmp = do_read_status(flash); + memcpy(&sr1, &tmp, 1); + tmp = w25q_read_status_register_2(flash); + memcpy(&sr2, &tmp, 1); + + msg_cdbg("%s: old status 2: 0x%02x\n", __func__, tmp); + + sr2.srp1 = enable ? 1 : 0; + + memcpy(&expected, &sr2, 1); + w25q_write_status_register_WREN(flash, *((uint8_t *)&sr1), *((uint8_t *)&sr2)); + + tmp = w25q_read_status_register_2(flash); + msg_cdbg("%s: new status 2: 0x%02x\n", __func__, tmp); + if ((tmp & MASK_WP2_AREA) != (expected & MASK_WP2_AREA)) + return 1; + + return 0; +} + +enum wp_mode get_wp_mode(const char *mode_str) +{ + enum wp_mode wp_mode = WP_MODE_UNKNOWN; + + if (!strcasecmp(mode_str, "hardware")) + wp_mode = WP_MODE_HARDWARE; + else if (!strcasecmp(mode_str, "power_cycle")) + wp_mode = WP_MODE_POWER_CYCLE; + else if (!strcasecmp(mode_str, "permanent")) + wp_mode = WP_MODE_PERMANENT; + + return wp_mode; +} + +static int w25q_disable_writeprotect(const struct flashctx *flash, + enum wp_mode wp_mode) +{ + int ret = 1; + struct w25q_status_2 sr2; + uint8_t tmp; + + switch (wp_mode) { + case WP_MODE_HARDWARE: + ret = w25_set_srp0(flash, 0); + break; + case WP_MODE_POWER_CYCLE: + tmp = w25q_read_status_register_2(flash); + memcpy(&sr2, &tmp, 1); + if (sr2.srp1) { + msg_cerr("%s(): must disconnect power to disable " + "write-protection\n", __func__); + } else { + ret = 0; + } + break; + case WP_MODE_PERMANENT: + msg_cerr("%s(): cannot disable permanent write-protection\n", + __func__); + break; + default: + msg_cerr("%s(): invalid mode specified\n", __func__); + break; + } + + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int w25q_disable_writeprotect_default(const struct flashctx *flash) +{ + return w25q_disable_writeprotect(flash, WP_MODE_HARDWARE); +} + +static int w25q_enable_writeprotect(const struct flashctx *flash, + enum wp_mode wp_mode) +{ + int ret = 1; + struct w25q_status sr1; + struct w25q_status_2 sr2; + uint8_t tmp; + + switch (wp_mode) { + case WP_MODE_HARDWARE: + if (w25q_disable_writeprotect(flash, WP_MODE_POWER_CYCLE)) { + msg_cerr("%s(): cannot disable power cycle WP mode\n", + __func__); + break; + } + + tmp = do_read_status(flash); + memcpy(&sr1, &tmp, 1); + if (sr1.srp0) + ret = 0; + else + ret = w25_set_srp0(flash, 1); + + break; + case WP_MODE_POWER_CYCLE: + if (w25q_disable_writeprotect(flash, WP_MODE_HARDWARE)) { + msg_cerr("%s(): cannot disable hardware WP mode\n", + __func__); + break; + } + + tmp = w25q_read_status_register_2(flash); + memcpy(&sr2, &tmp, 1); + if (sr2.srp1) + ret = 0; + else + ret = w25q_set_srp1(flash, 1); + + break; + case WP_MODE_PERMANENT: + tmp = do_read_status(flash); + memcpy(&sr1, &tmp, 1); + if (sr1.srp0 == 0) { + ret = w25_set_srp0(flash, 1); + if (ret) { + msg_perr("%s(): cannot enable SRP0 for " + "permanent WP\n", __func__); + break; + } + } + + tmp = w25q_read_status_register_2(flash); + memcpy(&sr2, &tmp, 1); + if (sr2.srp1 == 0) { + ret = w25q_set_srp1(flash, 1); + if (ret) { + msg_perr("%s(): cannot enable SRP1 for " + "permanent WP\n", __func__); + break; + } + } + + break; + default: + msg_perr("%s(): invalid mode %d\n", __func__, wp_mode); + break; + } + + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + return ret; +} + +/* W25P, W25X, and many flash chips from various vendors */ +struct wp wp_w25 = { + .list_ranges = w25_list_ranges, + .set_range = w25_set_range, + .enable = w25_enable_writeprotect, + .disable = w25_disable_writeprotect, + .wp_status = w25_wp_status, + +}; + +/* W25Q series has features such as a second status register and SFDP */ +struct wp wp_w25q = { + .list_ranges = w25_list_ranges, + .set_range = w25_set_range, + .enable = w25q_enable_writeprotect, + /* + * By default, disable hardware write-protection. We may change + * this later if we want to add fine-grained write-protect disable + * as a command-line option. + */ + .disable = w25q_disable_writeprotect_default, + .wp_status = w25q_wp_status, +}; + +/* W25Q large series has 4 block-protect bits */ +struct wp wp_w25q_large = { + .list_ranges = w25_list_ranges, + .set_range = w25q_large_set_range, + .enable = w25q_enable_writeprotect, + /* + * By default, disable hardware write-protection. We may change + * this later if we want to add fine-grained write-protect disable + * as a command-line option. + */ + .disable = w25q_disable_writeprotect_default, + .wp_status = w25q_large_wp_status, +}; + +struct wp_range_descriptor gd25q32_cmp0_ranges[] = { + /* none, bp4 and bp3 => don't care */ + { { }, 0x00, {0, 0} }, + { { }, 0x08, {0, 0} }, + { { }, 0x10, {0, 0} }, + { { }, 0x18, {0, 0} }, + + { { }, 0x01, {0x3f0000, 64 * 1024} }, + { { }, 0x02, {0x3e0000, 128 * 1024} }, + { { }, 0x03, {0x3c0000, 256 * 1024} }, + { { }, 0x04, {0x380000, 512 * 1024} }, + { { }, 0x05, {0x300000, 1024 * 1024} }, + { { }, 0x06, {0x200000, 2048 * 1024} }, + + { { }, 0x09, {0x000000, 64 * 1024} }, + { { }, 0x0a, {0x000000, 128 * 1024} }, + { { }, 0x0b, {0x000000, 256 * 1024} }, + { { }, 0x0c, {0x000000, 512 * 1024} }, + { { }, 0x0d, {0x000000, 1024 * 1024} }, + { { }, 0x0e, {0x000000, 2048 * 1024} }, + + /* all, bp4 and bp3 => don't care */ + { { }, 0x07, {0x000000, 4096 * 1024} }, + { { }, 0x0f, {0x000000, 4096 * 1024} }, + { { }, 0x17, {0x000000, 4096 * 1024} }, + { { }, 0x1f, {0x000000, 4096 * 1024} }, + + { { }, 0x11, {0x3ff000, 4 * 1024} }, + { { }, 0x12, {0x3fe000, 8 * 1024} }, + { { }, 0x13, {0x3fc000, 16 * 1024} }, + { { }, 0x14, {0x3f8000, 32 * 1024} }, /* bp0 => don't care */ + { { }, 0x15, {0x3f8000, 32 * 1024} }, /* bp0 => don't care */ + { { }, 0x16, {0x3f8000, 32 * 1024} }, + + { { }, 0x19, {0x000000, 4 * 1024} }, + { { }, 0x1a, {0x000000, 8 * 1024} }, + { { }, 0x1b, {0x000000, 16 * 1024} }, + { { }, 0x1c, {0x000000, 32 * 1024} }, /* bp0 => don't care */ + { { }, 0x1d, {0x000000, 32 * 1024} }, /* bp0 => don't care */ + { { }, 0x1e, {0x000000, 32 * 1024} }, +}; + +struct wp_range_descriptor gd25q32_cmp1_ranges[] = { + /* All, bp4 and bp3 => don't care */ + { { }, 0x00, {0x000000, 4096 * 1024} }, /* All */ + { { }, 0x08, {0x000000, 4096 * 1024} }, + { { }, 0x10, {0x000000, 4096 * 1024} }, + { { }, 0x18, {0x000000, 4096 * 1024} }, + + { { }, 0x01, {0x000000, 4032 * 1024} }, + { { }, 0x02, {0x000000, 3968 * 1024} }, + { { }, 0x03, {0x000000, 3840 * 1024} }, + { { }, 0x04, {0x000000, 3584 * 1024} }, + { { }, 0x05, {0x000000, 3 * 1024 * 1024} }, + { { }, 0x06, {0x000000, 2 * 1024 * 1024} }, + + { { }, 0x09, {0x010000, 4032 * 1024} }, + { { }, 0x0a, {0x020000, 3968 * 1024} }, + { { }, 0x0b, {0x040000, 3840 * 1024} }, + { { }, 0x0c, {0x080000, 3584 * 1024} }, + { { }, 0x0d, {0x100000, 3 * 1024 * 1024} }, + { { }, 0x0e, {0x200000, 2 * 1024 * 1024} }, + + /* None, bp4 and bp3 => don't care */ + { { }, 0x07, {0, 0} }, /* None */ + { { }, 0x0f, {0, 0} }, + { { }, 0x17, {0, 0} }, + { { }, 0x1f, {0, 0} }, + + { { }, 0x11, {0x000000, 4092 * 1024} }, + { { }, 0x12, {0x000000, 4088 * 1024} }, + { { }, 0x13, {0x000000, 4080 * 1024} }, + { { }, 0x14, {0x000000, 4064 * 1024} }, /* bp0 => don't care */ + { { }, 0x15, {0x000000, 4064 * 1024} }, /* bp0 => don't care */ + { { }, 0x16, {0x000000, 4064 * 1024} }, + + { { }, 0x19, {0x001000, 4092 * 1024} }, + { { }, 0x1a, {0x002000, 4088 * 1024} }, + { { }, 0x1b, {0x040000, 4080 * 1024} }, + { { }, 0x1c, {0x080000, 4064 * 1024} }, /* bp0 => don't care */ + { { }, 0x1d, {0x080000, 4064 * 1024} }, /* bp0 => don't care */ + { { }, 0x1e, {0x080000, 4064 * 1024} }, +}; + +static struct wp_context gd25q32_wp = { + /* TODO: map second status register */ + .sr1 = { .bp0_pos = 2, .bp_bits = 5, .srp_pos = 7 }, +}; + +struct wp_range_descriptor gd25q128_cmp0_ranges[] = { + /* none, bp4 and bp3 => don't care, others = 0 */ + { { .tb = 0 }, 0x00, {0, 0} }, + { { .tb = 0 }, 0x08, {0, 0} }, + { { .tb = 0 }, 0x10, {0, 0} }, + { { .tb = 0 }, 0x18, {0, 0} }, + + { { .tb = 0 }, 0x01, {0xfc0000, 256 * 1024} }, + { { .tb = 0 }, 0x02, {0xf80000, 512 * 1024} }, + { { .tb = 0 }, 0x03, {0xf00000, 1024 * 1024} }, + { { .tb = 0 }, 0x04, {0xe00000, 2048 * 1024} }, + { { .tb = 0 }, 0x05, {0xc00000, 4096 * 1024} }, + { { .tb = 0 }, 0x06, {0x800000, 8192 * 1024} }, + + { { .tb = 0 }, 0x09, {0x000000, 256 * 1024} }, + { { .tb = 0 }, 0x0a, {0x000000, 512 * 1024} }, + { { .tb = 0 }, 0x0b, {0x000000, 1024 * 1024} }, + { { .tb = 0 }, 0x0c, {0x000000, 2048 * 1024} }, + { { .tb = 0 }, 0x0d, {0x000000, 4096 * 1024} }, + { { .tb = 0 }, 0x0e, {0x000000, 8192 * 1024} }, + + /* all, bp4 and bp3 => don't care, others = 1 */ + { { .tb = 0 }, 0x07, {0x000000, 16384 * 1024} }, + { { .tb = 0 }, 0x0f, {0x000000, 16384 * 1024} }, + { { .tb = 0 }, 0x17, {0x000000, 16384 * 1024} }, + { { .tb = 0 }, 0x1f, {0x000000, 16384 * 1024} }, + + { { .tb = 0 }, 0x11, {0xfff000, 4 * 1024} }, + { { .tb = 0 }, 0x12, {0xffe000, 8 * 1024} }, + { { .tb = 0 }, 0x13, {0xffc000, 16 * 1024} }, + { { .tb = 0 }, 0x14, {0xff8000, 32 * 1024} }, /* bp0 => don't care */ + { { .tb = 0 }, 0x15, {0xff8000, 32 * 1024} }, /* bp0 => don't care */ + + { { .tb = 0 }, 0x19, {0x000000, 4 * 1024} }, + { { .tb = 0 }, 0x1a, {0x000000, 8 * 1024} }, + { { .tb = 0 }, 0x1b, {0x000000, 16 * 1024} }, + { { .tb = 0 }, 0x1c, {0x000000, 32 * 1024} }, /* bp0 => don't care */ + { { .tb = 0 }, 0x1d, {0x000000, 32 * 1024} }, /* bp0 => don't care */ + { { .tb = 0 }, 0x1e, {0x000000, 32 * 1024} }, +}; + +struct wp_range_descriptor gd25q128_cmp1_ranges[] = { + /* none, bp4 and bp3 => don't care, others = 0 */ + { { .tb = 1 }, 0x00, {0x000000, 16384 * 1024} }, + { { .tb = 1 }, 0x08, {0x000000, 16384 * 1024} }, + { { .tb = 1 }, 0x10, {0x000000, 16384 * 1024} }, + { { .tb = 1 }, 0x18, {0x000000, 16384 * 1024} }, + + { { .tb = 1 }, 0x01, {0x000000, 16128 * 1024} }, + { { .tb = 1 }, 0x02, {0x000000, 15872 * 1024} }, + { { .tb = 1 }, 0x03, {0x000000, 15360 * 1024} }, + { { .tb = 1 }, 0x04, {0x000000, 14336 * 1024} }, + { { .tb = 1 }, 0x05, {0x000000, 12288 * 1024} }, + { { .tb = 1 }, 0x06, {0x000000, 8192 * 1024} }, + + { { .tb = 1 }, 0x09, {0x000000, 16128 * 1024} }, + { { .tb = 1 }, 0x0a, {0x000000, 15872 * 1024} }, + { { .tb = 1 }, 0x0b, {0x000000, 15360 * 1024} }, + { { .tb = 1 }, 0x0c, {0x000000, 14336 * 1024} }, + { { .tb = 1 }, 0x0d, {0x000000, 12288 * 1024} }, + { { .tb = 1 }, 0x0e, {0x000000, 8192 * 1024} }, + + /* none, bp4 and bp3 => don't care, others = 1 */ + { { .tb = 1 }, 0x07, {0x000000, 16384 * 1024} }, + { { .tb = 1 }, 0x08, {0x000000, 16384 * 1024} }, + { { .tb = 1 }, 0x0f, {0x000000, 16384 * 1024} }, + { { .tb = 1 }, 0x17, {0x000000, 16384 * 1024} }, + { { .tb = 1 }, 0x1f, {0x000000, 16384 * 1024} }, + + { { .tb = 1 }, 0x11, {0x000000, 16380 * 1024} }, + { { .tb = 1 }, 0x12, {0x000000, 16376 * 1024} }, + { { .tb = 1 }, 0x13, {0x000000, 16368 * 1024} }, + { { .tb = 1 }, 0x14, {0x000000, 16352 * 1024} }, /* bp0 => don't care */ + { { .tb = 1 }, 0x15, {0x000000, 16352 * 1024} }, /* bp0 => don't care */ + + { { .tb = 1 }, 0x19, {0x001000, 16380 * 1024} }, + { { .tb = 1 }, 0x1a, {0x002000, 16376 * 1024} }, + { { .tb = 1 }, 0x1b, {0x004000, 16368 * 1024} }, + { { .tb = 1 }, 0x1c, {0x008000, 16352 * 1024} }, /* bp0 => don't care */ + { { .tb = 1 }, 0x1d, {0x008000, 16352 * 1024} }, /* bp0 => don't care */ + { { .tb = 1 }, 0x1e, {0x008000, 16352 * 1024} }, +}; + +static struct wp_context gd25q128_wp = { + /* TODO: map second and third status registers */ + .sr1 = { .bp0_pos = 2, .bp_bits = 5, .srp_pos = 7 }, +}; + +/* FIXME: MX25L6406 has same ID as MX25L6405D */ +struct wp_range_descriptor mx25l6406e_ranges[] = { + { { }, 0, {0, 0} }, /* none */ + { { }, 0x1, {0x7e0000, 64 * 2 * 1024} }, /* blocks 126-127 */ + { { }, 0x2, {0x7c0000, 64 * 4 * 1024} }, /* blocks 124-127 */ + { { }, 0x3, {0x7a0000, 64 * 8 * 1024} }, /* blocks 120-127 */ + { { }, 0x4, {0x700000, 64 * 16 * 1024} }, /* blocks 112-127 */ + { { }, 0x5, {0x600000, 64 * 32 * 1024} }, /* blocks 96-127 */ + { { }, 0x6, {0x400000, 64 * 64 * 1024} }, /* blocks 64-127 */ + + { { }, 0x7, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0x8, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0x9, {0x000000, 64 * 64 * 1024} }, /* blocks 0-63 */ + { { }, 0xa, {0x000000, 64 * 96 * 1024} }, /* blocks 0-95 */ + { { }, 0xb, {0x000000, 64 * 112 * 1024} }, /* blocks 0-111 */ + { { }, 0xc, {0x000000, 64 * 120 * 1024} }, /* blocks 0-119 */ + { { }, 0xd, {0x000000, 64 * 124 * 1024} }, /* blocks 0-123 */ + { { }, 0xe, {0x000000, 64 * 126 * 1024} }, /* blocks 0-125 */ + { { }, 0xf, {0x000000, 64 * 128 * 1024} }, /* all */ +}; + +static struct wp_context mx25l6406e_wp = { + .sr1 = { .bp0_pos = 2, .bp_bits = 4, .srp_pos = 7 }, + .descrs = &mx25l6406e_ranges[0], +}; + +struct wp_range_descriptor mx25l6495f_tb0_ranges[] = { + { { }, 0, {0, 0} }, /* none */ + { { }, 0x1, {0x7f0000, 64 * 1 * 1024} }, /* block 127 */ + { { }, 0x2, {0x7e0000, 64 * 2 * 1024} }, /* blocks 126-127 */ + { { }, 0x3, {0x7c0000, 64 * 4 * 1024} }, /* blocks 124-127 */ + + { { }, 0x4, {0x780000, 64 * 8 * 1024} }, /* blocks 120-127 */ + { { }, 0x5, {0x700000, 64 * 16 * 1024} }, /* blocks 112-127 */ + { { }, 0x6, {0x600000, 64 * 32 * 1024} }, /* blocks 96-127 */ + { { }, 0x7, {0x400000, 64 * 64 * 1024} }, /* blocks 64-127 */ + { { }, 0x8, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0x9, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xa, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xb, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xc, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xd, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xe, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xf, {0x000000, 64 * 128 * 1024} }, /* all */ +}; + +struct wp_range_descriptor mx25l6495f_tb1_ranges[] = { + { { }, 0, {0, 0} }, /* none */ + { { }, 0x1, {0x000000, 64 * 1 * 1024} }, /* block 0 */ + { { }, 0x2, {0x000000, 64 * 2 * 1024} }, /* blocks 0-1 */ + { { }, 0x3, {0x000000, 64 * 4 * 1024} }, /* blocks 0-3 */ + { { }, 0x4, {0x000000, 64 * 8 * 1024} }, /* blocks 0-7 */ + { { }, 0x5, {0x000000, 64 * 16 * 1024} }, /* blocks 0-15 */ + { { }, 0x6, {0x000000, 64 * 32 * 1024} }, /* blocks 0-31 */ + { { }, 0x7, {0x000000, 64 * 64 * 1024} }, /* blocks 0-63 */ + { { }, 0x8, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0x9, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xa, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xb, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xc, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xd, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xe, {0x000000, 64 * 128 * 1024} }, /* all */ + { { }, 0xf, {0x000000, 64 * 128 * 1024} }, /* all */ +}; + +static struct wp_context mx25l6495f_wp = { + .sr1 = { .bp0_pos = 2, .bp_bits = 4, .srp_pos = 7 }, +}; + +struct wp_range_descriptor mx25l25635f_tb0_ranges[] = { + { { }, 0, {0, 0} }, /* none */ + { { }, 0x1, {0x1ff0000, 64 * 1 * 1024} }, /* block 511 */ + { { }, 0x2, {0x1fe0000, 64 * 2 * 1024} }, /* blocks 510-511 */ + { { }, 0x3, {0x1fc0000, 64 * 4 * 1024} }, /* blocks 508-511 */ + { { }, 0x4, {0x1f80000, 64 * 8 * 1024} }, /* blocks 504-511 */ + { { }, 0x5, {0x1f00000, 64 * 16 * 1024} }, /* blocks 496-511 */ + { { }, 0x6, {0x1e00000, 64 * 32 * 1024} }, /* blocks 480-511 */ + { { }, 0x7, {0x1c00000, 64 * 64 * 1024} }, /* blocks 448-511 */ + { { }, 0x8, {0x1800000, 64 * 128 * 1024} }, /* blocks 384-511 */ + { { }, 0x9, {0x1000000, 64 * 256 * 1024} }, /* blocks 256-511 */ + { { }, 0xa, {0x0000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xb, {0x0000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xc, {0x0000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xd, {0x0000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xe, {0x0000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xf, {0x0000000, 64 * 512 * 1024} }, /* all */ +}; + +struct wp_range_descriptor mx25l25635f_tb1_ranges[] = { + { { }, 0, {0, 0} }, /* none */ + { { }, 0x1, {0x000000, 64 * 1 * 1024} }, /* block 0 */ + { { }, 0x2, {0x000000, 64 * 2 * 1024} }, /* blocks 0-1 */ + { { }, 0x3, {0x000000, 64 * 4 * 1024} }, /* blocks 0-3 */ + { { }, 0x4, {0x000000, 64 * 8 * 1024} }, /* blocks 0-7 */ + { { }, 0x5, {0x000000, 64 * 16 * 1024} }, /* blocks 0-15 */ + { { }, 0x6, {0x000000, 64 * 32 * 1024} }, /* blocks 0-31 */ + { { }, 0x7, {0x000000, 64 * 64 * 1024} }, /* blocks 0-63 */ + { { }, 0x8, {0x000000, 64 * 128 * 1024} }, /* blocks 0-127 */ + { { }, 0x9, {0x000000, 64 * 256 * 1024} }, /* blocks 0-255 */ + { { }, 0xa, {0x000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xb, {0x000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xc, {0x000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xd, {0x000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xe, {0x000000, 64 * 512 * 1024} }, /* all */ + { { }, 0xf, {0x000000, 64 * 512 * 1024} }, /* all */ +}; + +static struct wp_context mx25l25635f_wp = { + .sr1 = { .bp0_pos = 2, .bp_bits = 4, .srp_pos = 7 }, +}; + +#if 0 +struct wp_range_descriptor s25fs128s_ranges[] = { + { { .tb = 1 }, 0, {0, 0} }, /* none */ + { { .tb = 1 }, 0x1, {0x000000, 256 * 1024} }, /* lower 64th */ + { { .tb = 1 }, 0x2, {0x000000, 512 * 1024} }, /* lower 32nd */ + { { .tb = 1 }, 0x3, {0x000000, 1024 * 1024} }, /* lower 16th */ + { { .tb = 1 }, 0x4, {0x000000, 2048 * 1024} }, /* lower 8th */ + { { .tb = 1 }, 0x5, {0x000000, 4096 * 1024} }, /* lower 4th */ + { { .tb = 1 }, 0x6, {0x000000, 8192 * 1024} }, /* lower half */ + { { .tb = 1 }, 0x7, {0x000000, 16384 * 1024} }, /* all */ + + { { .tb = 0 }, 0, {0, 0} }, /* none */ + { { .tb = 0 }, 0x1, {0xfc0000, 256 * 1024} }, /* upper 64th */ + { { .tb = 0 }, 0x2, {0xf80000, 512 * 1024} }, /* upper 32nd */ + { { .tb = 0 }, 0x3, {0xf00000, 1024 * 1024} }, /* upper 16th */ + { { .tb = 0 }, 0x4, {0xe00000, 2048 * 1024} }, /* upper 8th */ + { { .tb = 0 }, 0x5, {0xc00000, 4096 * 1024} }, /* upper 4th */ + { { .tb = 0 }, 0x6, {0x800000, 8192 * 1024} }, /* upper half */ + { { .tb = 0 }, 0x7, {0x000000, 16384 * 1024} }, /* all */ +}; + +static struct wp_context s25fs128s_wp = { + .sr1 = { .bp0_pos = 2, .bp_bits = 3, .srp_pos = 7 }, + .get_modifier_bits = s25f_get_modifier_bits, + .set_modifier_bits = s25f_set_modifier_bits, +}; + + +struct wp_range_descriptor s25fl256s_ranges[] = { + { { .tb = 1 }, 0, {0, 0} }, /* none */ + { { .tb = 1 }, 0x1, {0x000000, 512 * 1024} }, /* lower 64th */ + { { .tb = 1 }, 0x2, {0x000000, 1024 * 1024} }, /* lower 32nd */ + { { .tb = 1 }, 0x3, {0x000000, 2048 * 1024} }, /* lower 16th */ + { { .tb = 1 }, 0x4, {0x000000, 4096 * 1024} }, /* lower 8th */ + { { .tb = 1 }, 0x5, {0x000000, 8192 * 1024} }, /* lower 4th */ + { { .tb = 1 }, 0x6, {0x000000, 16384 * 1024} }, /* lower half */ + { { .tb = 1 }, 0x7, {0x000000, 32768 * 1024} }, /* all */ + + { { .tb = 0 }, 0, {0, 0} }, /* none */ + { { .tb = 0 }, 0x1, {0x1f80000, 512 * 1024} }, /* upper 64th */ + { { .tb = 0 }, 0x2, {0x1f00000, 1024 * 1024} }, /* upper 32nd */ + { { .tb = 0 }, 0x3, {0x1e00000, 2048 * 1024} }, /* upper 16th */ + { { .tb = 0 }, 0x4, {0x1c00000, 4096 * 1024} }, /* upper 8th */ + { { .tb = 0 }, 0x5, {0x1800000, 8192 * 1024} }, /* upper 4th */ + { { .tb = 0 }, 0x6, {0x1000000, 16384 * 1024} }, /* upper half */ + { { .tb = 0 }, 0x7, {0x000000, 32768 * 1024} }, /* all */ +}; + +static struct wp_context s25fl256s_wp = { + .sr1 = { .bp0_pos = 2, .bp_bits = 3, .srp_pos = 7 }, + .get_modifier_bits = s25f_get_modifier_bits, + .set_modifier_bits = s25f_set_modifier_bits, +}; +#endif + +/* Given a flash chip, this function returns its writeprotect info. */ +static int generic_range_table(const struct flashctx *flash, + struct wp_context **wp, + int *num_entries) +{ + *wp = NULL; + *num_entries = 0; + + switch (flash->chip->manufacture_id) { + case GIGADEVICE_ID: + switch(flash->chip->model_id) { + + case GIGADEVICE_GD25LQ32: + case GIGADEVICE_GD25Q32: { + uint8_t sr1 = w25q_read_status_register_2(flash); + *wp = &gd25q32_wp; + + if (!(sr1 & (1 << 6))) { /* CMP == 0 */ + (*wp)->descrs = &gd25q32_cmp0_ranges[0]; + *num_entries = ARRAY_SIZE(gd25q32_cmp0_ranges); + } else { /* CMP == 1 */ + (*wp)->descrs = &gd25q32_cmp1_ranges[0]; + *num_entries = ARRAY_SIZE(gd25q32_cmp1_ranges); + } + + break; + } + case GIGADEVICE_GD25Q128: + case GIGADEVICE_GD25LQ128CD: { + uint8_t sr1 = w25q_read_status_register_2(flash); + *wp = &gd25q128_wp; + + if (!(sr1 & (1 << 6))) { /* CMP == 0 */ + (*wp)->descrs = &gd25q128_cmp0_ranges[0]; + *num_entries = ARRAY_SIZE(gd25q128_cmp0_ranges); + } else { /* CMP == 1 */ + (*wp)->descrs = &gd25q128_cmp1_ranges[0]; + *num_entries = ARRAY_SIZE(gd25q128_cmp1_ranges); + } + + break; + } + default: + msg_cerr("%s() %d: GigaDevice flash chip mismatch" + " (0x%04x), aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + case MACRONIX_ID: + switch (flash->chip->model_id) { + case MACRONIX_MX25L6405: + /* FIXME: MX25L64* chips have mixed capabilities and + share IDs */ + *wp = &mx25l6406e_wp; + *num_entries = ARRAY_SIZE(mx25l6406e_ranges); + break; + case MACRONIX_MX25L6495F: { + uint8_t cr = mx25l_read_config_register(flash); + + *wp = &mx25l6495f_wp; + if (!(cr & (1 << 3))) { /* T/B == 0 */ + (*wp)->descrs = &mx25l6495f_tb0_ranges[0]; + *num_entries = ARRAY_SIZE(mx25l6495f_tb0_ranges); + } else { /* T/B == 1 */ + (*wp)->descrs = &mx25l6495f_tb1_ranges[0]; + *num_entries = ARRAY_SIZE(mx25l6495f_tb1_ranges); + } + break; + } + case MACRONIX_MX25L25635F: { + uint8_t cr = mx25l_read_config_register(flash); + + *wp = &mx25l25635f_wp; + if (!(cr & (1 << 3))) { /* T/B == 0 */ + (*wp)->descrs = &mx25l25635f_tb0_ranges[0]; + *num_entries = ARRAY_SIZE(mx25l25635f_tb0_ranges); + } else { /* T/B == 1 */ + (*wp)->descrs = &mx25l25635f_tb1_ranges[0]; + *num_entries = ARRAY_SIZE(mx25l25635f_tb1_ranges); + } + break; + } + default: + msg_cerr("%s():%d: MXIC flash chip mismatch (0x%04x)" + ", aborting\n", __func__, __LINE__, + flash->chip->model_id); + return -1; + } + break; + //case SPANSION_ID: + // switch (flash->chip->model_id) { + // case SPANSION_S25FS128S_L: + // case SPANSION_S25FS128S_S: { + // *wp = &s25fs128s_wp; + // (*wp)->descrs = s25fs128s_ranges; + // *num_entries = ARRAY_SIZE(s25fs128s_ranges); + // break; + // } + // case SPANSION_S25FL256S_UL: + // case SPANSION_S25FL256S_US: { + // *wp = &s25fl256s_wp; + // (*wp)->descrs = s25fl256s_ranges; + // *num_entries = ARRAY_SIZE(s25fl256s_ranges); + // break; + // } + // default: + // msg_cerr("%s():%d Spansion flash chip mismatch (0x%04x)" + // ", aborting\n", __func__, __LINE__, + // flash->chip->model_id); + // return -1; + // } + // break; + default: + msg_cerr("%s: flash vendor (0x%x) not found, aborting\n", + __func__, flash->chip->manufacture_id); + return -1; + } + + return 0; +} + +static uint8_t generic_get_bp_mask(struct wp_context *wp) +{ + return ((1 << (wp->sr1.bp0_pos + wp->sr1.bp_bits)) - 1) ^ \ + ((1 << wp->sr1.bp0_pos) - 1); +} + +static uint8_t generic_get_status_check_mask(struct wp_context *wp) +{ + return generic_get_bp_mask(wp) | 1 << wp->sr1.srp_pos; +} + +/* Given a [start, len], this function finds a block protect bit combination + * (if possible) and sets the corresponding bits in "status". Remaining bits + * are preserved. */ +static int generic_range_to_status(const struct flashctx *flash, + unsigned int start, unsigned int len, + uint8_t *status, uint8_t *check_mask) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int i, range_found = 0, num_entries; + uint8_t bp_mask; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + bp_mask = generic_get_bp_mask(wp); + + for (i = 0, r = &wp->descrs[0]; i < num_entries; i++, r++) { + msg_cspew("comparing range 0x%x 0x%x / 0x%x 0x%x\n", + start, len, r->range.start, r->range.len); + if ((start == r->range.start) && (len == r->range.len)) { + *status &= ~(bp_mask); + *status |= r->bp << (wp->sr1.bp0_pos); + + if (wp->set_modifier_bits) { + if (wp->set_modifier_bits(flash, &r->m) < 0) { + msg_cerr("error setting modifier " + "bits for range.\n"); + return -1; + } + } + + range_found = 1; + break; + } + } + + if (!range_found) { + msg_cerr("%s: matching range not found\n", __func__); + return -1; + } + + *check_mask = generic_get_status_check_mask(wp); + return 0; +} + +static int generic_status_to_range(const struct flashctx *flash, + const uint8_t sr1, unsigned int *start, unsigned int *len) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int num_entries, i, status_found = 0; + uint8_t sr1_bp; + struct modifier_bits m; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + /* modifier bits may be compared more than once, so get them here */ + if (wp->get_modifier_bits && wp->get_modifier_bits(flash, &m) < 0) + return -1; + + sr1_bp = (sr1 >> wp->sr1.bp0_pos) & ((1 << wp->sr1.bp_bits) - 1); + + for (i = 0, r = &wp->descrs[0]; i < num_entries; i++, r++) { + if (wp->get_modifier_bits) { + if (memcmp(&m, &r->m, sizeof(m))) + continue; + } + msg_cspew("comparing 0x%02x 0x%02x\n", sr1_bp, r->bp); + if (sr1_bp == r->bp) { + *start = r->range.start; + *len = r->range.len; + status_found = 1; + break; + } + } + + if (!status_found) { + msg_cerr("matching status not found\n"); + return -1; + } + + return 0; +} + +/* Given a [start, len], this function calls generic_range_to_status() to + * convert it to flash-chip-specific range bits, then sets into status register. + */ +static int generic_set_range(const struct flashctx *flash, + unsigned int start, unsigned int len) +{ + uint8_t status, expected, check_mask; + + status = do_read_status(flash); + msg_cdbg("%s: old status: 0x%02x\n", __func__, status); + + expected = status; /* preserve non-bp bits */ + if (generic_range_to_status(flash, start, len, &expected, &check_mask)) + return -1; + + do_write_status(flash, expected); + + status = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, status); + if ((status & check_mask) != (expected & check_mask)) { + msg_cerr("expected=0x%02x, but actual=0x%02x. check mask=0x%02x\n", + expected, status, check_mask); + return 1; + } + return 0; +} + +/* Set/clear the status regsiter write protect bit in SR1. */ +static int generic_set_srp0(const struct flashctx *flash, int enable) +{ + uint8_t status, expected, check_mask; + struct wp_context *wp; + int num_entries; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + expected = do_read_status(flash); + msg_cdbg("%s: old status: 0x%02x\n", __func__, expected); + + if (enable) + expected |= 1 << wp->sr1.srp_pos; + else + expected &= ~(1 << wp->sr1.srp_pos); + + do_write_status(flash, expected); + + status = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, status); + + check_mask = generic_get_status_check_mask(wp); + msg_cdbg("%s: check mask: 0x%02x\n", __func__, check_mask); + if ((status & check_mask) != (expected & check_mask)) { + msg_cerr("expected=0x%02x, but actual=0x%02x. check mask=0x%02x\n", + expected, status, check_mask); + return -1; + } + + return 0; +} + +static int generic_enable_writeprotect(const struct flashctx *flash, + enum wp_mode wp_mode) +{ + int ret; + + if (wp_mode != WP_MODE_HARDWARE) { + msg_cerr("%s(): unsupported write-protect mode\n", __func__); + return 1; + } + + ret = generic_set_srp0(flash, 1); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int generic_disable_writeprotect(const struct flashctx *flash) +{ + int ret; + + ret = generic_set_srp0(flash, 0); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int generic_list_ranges(const struct flashctx *flash) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int i, num_entries; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + r = &wp->descrs[0]; + for (i = 0; i < num_entries; i++) { + msg_cinfo("start: 0x%06x, length: 0x%06x\n", + r->range.start, r->range.len); + r++; + } + + return 0; +} + +static int wp_context_status(const struct flashctx *flash) +{ + uint8_t sr1; + unsigned int start, len; + int ret = 0; + struct wp_context *wp; + int num_entries, wp_en; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + sr1 = do_read_status(flash); + wp_en = (sr1 >> wp->sr1.srp_pos) & 1; + + msg_cinfo("WP: status: 0x%04x\n", sr1); + msg_cinfo("WP: status.srp0: %x\n", wp_en); + /* FIXME: SRP1 is not really generic, but we probably should print + * it anyway to have consistent output. #legacycruft */ + msg_cinfo("WP: status.srp1: %x\n", 0); + msg_cinfo("WP: write protect is %s.\n", + wp_en ? "enabled" : "disabled"); + + msg_cinfo("WP: write protect range: "); + if (generic_status_to_range(flash, sr1, &start, &len)) { + msg_cinfo("(cannot resolve the range)\n"); + ret = -1; + } else { + msg_cinfo("start=0x%08x, len=0x%08x\n", start, len); + } + + return ret; +} + +struct wp wp_generic = { + .list_ranges = generic_list_ranges, + .set_range = generic_set_range, + .enable = generic_enable_writeprotect, + .disable = generic_disable_writeprotect, + .wp_status = wp_context_status, +}; diff --git a/writeprotect.h b/writeprotect.h new file mode 100644 index 0000000..d39b993 --- /dev/null +++ b/writeprotect.h @@ -0,0 +1,57 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __WRITEPROTECT_H__ +#define __WRITEPROTECT_H__ 1 + +enum wp_mode { + WP_MODE_UNKNOWN = -1, + WP_MODE_HARDWARE, /* hardware WP pin determines status */ + WP_MODE_POWER_CYCLE, /* WP active until power off/on cycle */ + WP_MODE_PERMANENT, /* status register permanently locked, + WP permanently enabled */ +}; + +struct wp { + int (*list_ranges)(const struct flashctx *flash); + int (*set_range)(const struct flashctx *flash, + unsigned int start, unsigned int len); + int (*enable)(const struct flashctx *flash, enum wp_mode mode); + int (*disable)(const struct flashctx *flash); + int (*wp_status)(const struct flashctx *flash); +}; + +/* winbond w25-series */ +extern struct wp wp_w25; /* older winbond chips (w25p, w25x, etc) */ +extern struct wp wp_w25q; +extern struct wp wp_w25q_large; /* large winbond chips (>= 32MB) */ + +extern struct wp wp_generic; +extern struct wp wp_wpce775x; + +enum wp_mode get_wp_mode(const char *mode_str); + +/* + * Generic write-protect stuff + */ + +struct modifier_bits { + int sec; /* if 1, bp bits describe sectors */ + int tb; /* value of top/bottom select bit */ +}; + +#endif /* !__WRITEPROTECT_H__ */
Angel Pons has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 2:
(2 comments)
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c@15 PS2, Line 15: * empty line in comment
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c@140 PS2, Line 140: struct wp_range_descriptor en25f40_ranges[] = { Maybe move these to its own file? It will make this file more navigable
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 2:
(1 comment)
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c@140 PS2, Line 140: struct wp_range_descriptor en25f40_ranges[] = {
Maybe move these to its own file? It will make this file more navigable
they are only used in this implementation, I think you want the reverse here and them to be static.
Angel Pons has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 2:
(1 comment)
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c@140 PS2, Line 140: struct wp_range_descriptor en25f40_ranges[] = {
they are only used in this implementation, I think you want the reverse here and them to be static.
Well, that would be nice as well. My idea is to have tables in a separate file, so that the code doesn't get lost among the many tables
Nikolai Artemiev has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 6:
(3 comments)
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c@718 PS6, Line 718: start = strtoul(argv[optind], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind]); : ret = 1; : goto out_shutdown; : } : : len = strtoul(argv[optind + 1], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind + 1]); I think we should avoid using `argv[optind]` and `argv[optind + 1]` to get the values here. It currently won't work at all since the two arguments will look like extraneous tokens and cause the program to abort on lines 408-409.
It's tricky since getopt_long() can't parse two arguments for a single flag. We could either: - Read the two tokens immediately after parsing "--wp-range" - Use getopt_long() to parse a single token with both values e.g. "--wp-range start,len"
https://review.coreboot.org/c/flashrom/+/40325/6/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/6/writeprotect.c@851 PS6, Line 851: /* FIXME: Move to spi25.c if it's a JEDEC standard opcode */ Should this fn be moved?
https://review.coreboot.org/c/flashrom/+/40325/6/writeprotect.c@872 PS6, Line 872: /* FIXME: Move to spi25.c if it's a JEDEC standard opcode */ Ditto
David Hendricks has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 6:
(1 comment)
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c@718 PS6, Line 718: start = strtoul(argv[optind], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind]); : ret = 1; : goto out_shutdown; : } : : len = strtoul(argv[optind + 1], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind + 1]);
I think we should avoid using `argv[optind]` and `argv[optind + 1]` to get the values here. […]
Yikes... Yeah, this is pretty suspicious. This would be a good time to change the syntax to "--wp-range start,len" if desired, however if that will break a lot of tools that use --wp-range then the first suggestion seems fine too (read the tokens in the OPTION_WP_SET_RANGE case and set optind as needed).
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 6:
(1 comment)
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c@718 PS6, Line 718: start = strtoul(argv[optind], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind]); : ret = 1; : goto out_shutdown; : } : : len = strtoul(argv[optind + 1], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind + 1]);
Yikes... Yeah, this is pretty suspicious. […]
* Beyond the technical I would just like to state my position while we are upstreaming this stuff from the ChromiumOS fork:
I would be willing - in the interest of community spirt, correct and quality engineering - to do whatever needs to be done correctly in upstream and deal with ChromiumOS's mitigration path locally downstream. It isn't fair on the community to bring bargage from Google into upstream and so I think we should shoulder the responsibility of that locally.
* As for the technical:
I would then be inclinded to follow Nikolai second cleaner solution and agree with Hendricks to make the bold move to a new preidctable syntax that is determistically parsable.
Nico Huber has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 6:
Did somebody look into Hatim's patches [1]? I didn't but feel like we owe them some attention. They cover more than just write protection. I fear, if we don't look into them now, we never will.
[1] Somewhere amidst https://patchwork.coreboot.org/project/flashrom/list/
David Hendricks has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 6:
Patch Set 6:
Did somebody look into Hatim's patches [1]? I didn't but feel like we owe them some attention. They cover more than just write protection. I fear, if we don't look into them now, we never will.
[1] Somewhere amidst https://patchwork.coreboot.org/project/flashrom/list/
I looked into some of them way back when they were first written, but wasn't doing much work with flashrom at the time so I didn't get involved much. IIRC the only issue I had was that some patches were too generalized, i.e. they made assumptions that were true for some chips but not all. Some large Spansion S25FS and FL chips were particularly odd, and there were cases where "don't care" block protect bits could impact generic methods unexpectedly.
Still, he did a lot of good work and it may be useful to revisit those patches to see if we can salvage some of them. And as you mention they cover more than write protection, there might be some OTP-related patches we can use.
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 6:
(1 comment)
Patch Set 6:
Patch Set 6:
Did somebody look into Hatim's patches [1]? I didn't but feel like we owe them some attention. They cover more than just write protection. I fear, if we don't look into them now, we never will.
[1] Somewhere amidst https://patchwork.coreboot.org/project/flashrom/list/
I looked into some of them way back when they were first written, but wasn't doing much work with flashrom at the time so I didn't get involved much. IIRC the only issue I had was that some patches were too generalized, i.e. they made assumptions that were true for some chips but not all. Some large Spansion S25FS and FL chips were particularly odd, and there were cases where "don't care" block protect bits could impact generic methods unexpectedly.
Still, he did a lot of good work and it may be useful to revisit those patches to see if we can salvage some of them. And as you mention they cover more than write protection, there might be some OTP-related patches we can use.
After looking at them for a bit I think if we can first work out how to integrate this relatively self-contained support that actually working in the field we can massage forwards into the more generalised version on the list. Otherwise we are forever stuck between a working self-contained abeit not ideal support or a idealised support that isn't completed yet. Since there is a reasonable amount of work to fully get to the end of the road there we need to lay some ground work down.
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c@718 PS6, Line 718: start = strtoul(argv[optind], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind]); : ret = 1; : goto out_shutdown; : } : : len = strtoul(argv[optind + 1], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind + 1]);
- Beyond the technical I would just like to state my position while we are upstreaming this stuff fr […]
I purpose the following downstream:
https://chromium-review.googlesource.com/c/chromiumos/third_party/flashrom/+...
Hello build bot (Jenkins), Matt DeVillier, Nikolai Artemiev,
I'd like you to reexamine a change. Please visit
https://review.coreboot.org/c/flashrom/+/40325
to look at the new patch set (#7).
Change subject: Add writeprotect support ......................................................................
Add writeprotect support
BUG=b:153800563 BRANCH=none TEST=builds
Change-Id: Id93b5a1cb2da476fa8a7dde41d7b963024117474 Signed-off-by: Edward O'Callaghan quasisec@google.com --- M Makefile M cli_classic.c M flash.h M meson.build A writeprotect.c A writeprotect.h 6 files changed, 2,733 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/25/40325/7
Angel Pons has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 7: Code-Review+1
(5 comments)
I'm not sure if this could be split up a bit.
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.h File writeprotect.h:
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.h@44 PS7, Line 44: wp_wpce775x Do we have this programmer upstream?
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@873 PS7, Line 873: uint8_t mx25l_read_config_register(const struct flashctx *flash);//XXX Hmm?
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@1132 PS7, Line 1132: //case ATMEL_AT25SF128A: Hm?
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@1473 PS7, Line 1473: /* FIXME: this is NOT endian-free copy. */ How about providing a union instead of a struct? Here's an example: https://github.com/coreboot/coreboot/blob/dc0c08100124278efc9ed91952378b0190...
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@2236 PS7, Line 2236: //case SPANSION_ID: : // switch (flash->chip->model_id) { : // case SPANSION_S25FS128S_L: : // case SPANSION_S25FS128S_S: { : // *wp = &s25fs128s_wp; : // (*wp)->descrs = s25fs128s_ranges; : // *num_entries = ARRAY_SIZE(s25fs128s_ranges); : // break; : // } : // case SPANSION_S25FL256S_UL: : // case SPANSION_S25FL256S_US: { : // *wp = &s25fl256s_wp; : // (*wp)->descrs = s25fl256s_ranges; : // *num_entries = ARRAY_SIZE(s25fl256s_ranges); : // break; : // } : // default: : // msg_cerr("%s():%d Spansion flash chip mismatch (0x%04x)" : // ", aborting\n", __func__, __LINE__, : // flash->chip->model_id); : // return -1; : // } : // break; Um?
Angel Pons has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support ......................................................................
Patch Set 7: Code-Review-1
Uhm, looks like something isn't right...
Found Winbond flash chip "W25Q64.V" (8192 kB, SPI) mapped at physical address 0x00000000ff800000. No data in flash context! Error: write protect is not supported on this flash chip.
https://paste.flashrom.org/view.php?id=3361
Hello build bot (Jenkins), Matt DeVillier, Angel Pons, Nikolai Artemiev,
I'd like you to reexamine a change. Please visit
https://review.coreboot.org/c/flashrom/+/40325
to look at the new patch set (#8).
Change subject: Add writeprotect support infrastructure ......................................................................
Add writeprotect support infrastructure
The following just lays out the structure for write protect manipulation of SPI flash chips in Flashrom. We later follow up with adding support for each manufacturer group.
BUG=b:153800563 BRANCH=none TEST=builds
Change-Id: Id93b5a1cb2da476fa8a7dde41d7b963024117474 Signed-off-by: Edward O'Callaghan quasisec@google.com --- M Makefile M cli_classic.c M flash.h M meson.build A writeprotect.c A writeprotect.h 6 files changed, 610 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/25/40325/8
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 8:
(5 comments)
Patch Set 7: Code-Review-1
Uhm, looks like something isn't right...
Found Winbond flash chip "W25Q64.V" (8192 kB, SPI) mapped at physical address 0x00000000ff800000. No data in flash context! Error: write protect is not supported on this flash chip.
I have not seen that before however lets deal with it in the winbond support patch as a follow up as there is a fair amount of work here to get this split up and cleaned up. Thanks for the report though I will try to EM100 locally to repro the issue
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.h File writeprotect.h:
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.h@44 PS7, Line 44: wp_wpce775x
Do we have this programmer upstream?
It's coming once I have it cleaned up locally, CrOS code quality sucks.
I removed it out of this commit and made it into a much simpler commit to deal with the writeprotect functionality scope.
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@873 PS7, Line 873: uint8_t mx25l_read_config_register(const struct flashctx *flash);//XXX
Hmm?
poor cros code quality, was required to make it compile upstream. Removed from commit for now.
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@1132 PS7, Line 1132: //case ATMEL_AT25SF128A:
Hm?
Removed for now.
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@1473 PS7, Line 1473: /* FIXME: this is NOT endian-free copy. */
How about providing a union instead of a struct? Here's an example: https://github. […]
I have moved the winbond support into a follow up commit to deal with it separately from the wp infrastructure.
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@2236 PS7, Line 2236: //case SPANSION_ID: : // switch (flash->chip->model_id) { : // case SPANSION_S25FS128S_L: : // case SPANSION_S25FS128S_S: { : // *wp = &s25fs128s_wp; : // (*wp)->descrs = s25fs128s_ranges; : // *num_entries = ARRAY_SIZE(s25fs128s_ranges); : // break; : // } : // case SPANSION_S25FL256S_UL: : // case SPANSION_S25FL256S_US: { : // *wp = &s25fl256s_wp; : // (*wp)->descrs = s25fl256s_ranges; : // *num_entries = ARRAY_SIZE(s25fl256s_ranges); : // break; : // } : // default: : // msg_cerr("%s():%d Spansion flash chip mismatch (0x%04x)" : // ", aborting\n", __func__, __LINE__, : // flash->chip->model_id); : // return -1; : // } : // break;
Um?
Missing chips upstream, removed for now.
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 8:
(1 comment)
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.h File writeprotect.h:
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.h@44 PS7, Line 44: wp_wpce775x
It's coming once I have it cleaned up locally, CrOS code quality sucks. […]
.
Stefan Reinauer has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 8: Code-Review+2
Angel Pons has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 8:
(6 comments)
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@115 PS8, Line 115: char *endptr = NULL, *token = NULL; : : if (!optarg) { : msg_gerr("Error: No wp-range values provided\n"); : return -1; : } : : token = strtok(optarg, ","); : if (!token) { : msg_gerr("Error: Invalid wp-range argument format\n"); : return -1; : } : *start = strtoul(token, &endptr, 0); : : token = strtok(NULL, ","); : if (!token) { : msg_gerr("Error: Invalid wp-range argument format\n"); : return -1; : } : *len = strtoul(token, &endptr, 0); : : return 0; One tab too many?
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@153 PS8, Line 153: set_wp_disable = 0, wp_status = 0, wp_list = 0; Missing `int` (yes, I know it can work like this, but please be consistent)
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@639 PS8, Line 639: Please, no space here
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@671 PS8, Line 671: "on this flash chip.\n"); Please never break strings. It kills grep-ability. https://flashrom.org/Development_Guidelines#Coding_style
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@726 PS8, Line 726: if (set_wp_range && set_wp_region) { This can be handled much earlier, around where `set_wp_enable && set_wp_disable` is checked.
https://review.coreboot.org/c/flashrom/+/40325/8/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/8/writeprotect.c@188 PS8, Line 188: msg_cerr("error setting modifier " Another string that should not be broken.
Hello build bot (Jenkins), Matt DeVillier, Stefan Reinauer, Angel Pons, Nikolai Artemiev,
I'd like you to reexamine a change. Please visit
https://review.coreboot.org/c/flashrom/+/40325
to look at the new patch set (#9).
Change subject: Add writeprotect support infrastructure ......................................................................
Add writeprotect support infrastructure
The following just lays out the structure for write protect manipulation of SPI flash chips in Flashrom. We later follow up with adding support for each manufacturer group.
BUG=b:153800563 BRANCH=none TEST=builds
Change-Id: Id93b5a1cb2da476fa8a7dde41d7b963024117474 Signed-off-by: Edward O'Callaghan quasisec@google.com --- M Makefile M cli_classic.c M flash.h M meson.build A writeprotect.c A writeprotect.h 6 files changed, 602 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/25/40325/9
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 8:
(9 comments)
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@115 PS8, Line 115: char *endptr = NULL, *token = NULL; : : if (!optarg) { : msg_gerr("Error: No wp-range values provided\n"); : return -1; : } : : token = strtok(optarg, ","); : if (!token) { : msg_gerr("Error: Invalid wp-range argument format\n"); : return -1; : } : *start = strtoul(token, &endptr, 0); : : token = strtok(NULL, ","); : if (!token) { : msg_gerr("Error: Invalid wp-range argument format\n"); : return -1; : } : *len = strtoul(token, &endptr, 0); : : return 0;
One tab too many?
Done
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@153 PS8, Line 153: set_wp_disable = 0, wp_status = 0, wp_list = 0;
Missing `int` (yes, I know it can work like this, but please be consistent)
Done
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@639 PS8, Line 639:
Please, no space here
Done
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@671 PS8, Line 671: "on this flash chip.\n");
Please never break strings. It kills grep-ability. https://flashrom. […]
Done
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@682 PS8, Line 682: r("Error: write protect is not supported " : "on this flash chip.\n"); also fixed.
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@706 PS8, Line 706: msg_gerr("Error: write protect is not supported " : "on this flash chip.\n"); also fixed.
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@718 PS8, Line 718: msg_gerr("Error: write protect is not supported " : "on this flash chip.\n"); : ret = 1; also fixed.
https://review.coreboot.org/c/flashrom/+/40325/8/cli_classic.c@726 PS8, Line 726: if (set_wp_range && set_wp_region) {
This can be handled much earlier, around where `set_wp_enable && set_wp_disable` is checked.
Done and simplified the logic as well, thanks.
https://review.coreboot.org/c/flashrom/+/40325/8/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/8/writeprotect.c@188 PS8, Line 188: msg_cerr("error setting modifier "
Another string that should not be broken.
Done
Hello build bot (Jenkins), Matt DeVillier, Stefan Reinauer, Angel Pons, Nikolai Artemiev,
I'd like you to reexamine a change. Please visit
https://review.coreboot.org/c/flashrom/+/40325
to look at the new patch set (#10).
Change subject: Add writeprotect support infrastructure ......................................................................
Add writeprotect support infrastructure
The following just lays out the structure for write protect manipulation of SPI flash chips in Flashrom. We later follow up with adding support for each manufacturer group.
BUG=b:153800563 BRANCH=none TEST=builds
Change-Id: Id93b5a1cb2da476fa8a7dde41d7b963024117474 Signed-off-by: Edward O'Callaghan quasisec@google.com --- M Makefile M cli_classic.c M flash.h M meson.build A writeprotect.c A writeprotect.h 6 files changed, 602 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/25/40325/10
Angel Pons has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 10: Code-Review+2
(1 comment)
Thank you!
https://review.coreboot.org/c/flashrom/+/40325/10/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/10/cli_classic.c@656 PS10, Line 656: fill_flash->chip->wp This can be done in another patch, but I think we can test this condition for all write-protect operations in a single place for simplicity. I'll look into it.
Hello build bot (Jenkins), Matt DeVillier, Stefan Reinauer, Angel Pons, Nikolai Artemiev,
I'd like you to reexamine a change. Please visit
https://review.coreboot.org/c/flashrom/+/40325
to look at the new patch set (#11).
Change subject: Add writeprotect support infrastructure ......................................................................
Add writeprotect support infrastructure
The following just lays out the structure for write protect manipulation of SPI flash chips in Flashrom. We later follow up with adding support for each manufacturer group.
BUG=b:153800563 BRANCH=none TEST=builds
Change-Id: Id93b5a1cb2da476fa8a7dde41d7b963024117474 Signed-off-by: Edward O'Callaghan quasisec@google.com --- M Makefile M cli_classic.c M flash.h M meson.build A writeprotect.c A writeprotect.h 6 files changed, 602 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/25/40325/11
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 11:
(6 comments)
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c File cli_classic.c:
https://review.coreboot.org/c/flashrom/+/40325/6/cli_classic.c@718 PS6, Line 718: start = strtoul(argv[optind], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind]); : ret = 1; : goto out_shutdown; : } : : len = strtoul(argv[optind + 1], &endptr, 0); : if (errno == ERANGE || errno == EINVAL || *endptr != '\0') { : msg_gerr("Error: value "%s" invalid\n", argv[optind + 1]);
I purpose the following downstream: […]
Ack
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c@15 PS2, Line 15: *
empty line in comment
Ack
https://review.coreboot.org/c/flashrom/+/40325/2/writeprotect.c@140 PS2, Line 140: struct wp_range_descriptor en25f40_ranges[] = {
Well, that would be nice as well. […]
Ack
https://review.coreboot.org/c/flashrom/+/40325/6/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/6/writeprotect.c@851 PS6, Line 851: /* FIXME: Move to spi25.c if it's a JEDEC standard opcode */
Should this fn be moved?
Done
https://review.coreboot.org/c/flashrom/+/40325/6/writeprotect.c@872 PS6, Line 872: /* FIXME: Move to spi25.c if it's a JEDEC standard opcode */
Ditto
Done
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c File writeprotect.c:
https://review.coreboot.org/c/flashrom/+/40325/7/writeprotect.c@1473 PS7, Line 1473: /* FIXME: this is NOT endian-free copy. */
I have moved the winbond support into a follow up commit to deal with it separately from the wp infr […]
Ack
Edward O'Callaghan has submitted this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Add writeprotect support infrastructure
The following just lays out the structure for write protect manipulation of SPI flash chips in Flashrom. We later follow up with adding support for each manufacturer group.
BUG=b:153800563 BRANCH=none TEST=builds
Change-Id: Id93b5a1cb2da476fa8a7dde41d7b963024117474 Signed-off-by: Edward O'Callaghan quasisec@google.com Reviewed-on: https://review.coreboot.org/c/flashrom/+/40325 Reviewed-by: Angel Pons th3fanbus@gmail.com Tested-by: build bot (Jenkins) no-reply@coreboot.org --- M Makefile M cli_classic.c M flash.h M meson.build A writeprotect.c A writeprotect.h 6 files changed, 602 insertions(+), 2 deletions(-)
Approvals: build bot (Jenkins): Verified Angel Pons: Looks good to me, approved
diff --git a/Makefile b/Makefile index e475cbd..8af7042 100644 --- a/Makefile +++ b/Makefile @@ -638,7 +638,7 @@ CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \ sst28sf040.o 82802ab.o \ sst49lfxxxc.o sst_fwhub.o edi.o flashchips.o spi.o spi25.o spi25_statusreg.o \ - spi95.o opaque.o sfdp.o en29lv640b.o at45db.o + spi95.o opaque.o sfdp.o en29lv640b.o at45db.o writeprotect.o
############################################################################### # Library code. diff --git a/cli_classic.c b/cli_classic.c index 967ff50..ae7f6ef 100644 --- a/cli_classic.c +++ b/cli_classic.c @@ -17,6 +17,7 @@ * GNU General Public License for more details. */
+#include <errno.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> @@ -27,6 +28,7 @@ #include "flashchips.h" #include "fmap.h" #include "programmer.h" +#include "writeprotect.h" #include "libflashrom.h"
static void cli_classic_usage(const char *name) @@ -54,6 +56,11 @@ " -n | --noverify don't auto-verify\n" " -N | --noverify-all verify included regions only (cf. -i)\n" " -l | --layout <layoutfile> read ROM layout from <layoutfile>\n" + " --wp-disable disable write protection\n" + " --wp-enable enable write protection\n" + " --wp-list list write protect range\n" + " --wp-status show write protect status\n" + " --wp-range=<start>,<len> set write protect range\n" " --flash-name read out the detected flash name\n" " --flash-size read out the detected flash size\n" " --fmap read ROM layout from fmap embedded in ROM\n" @@ -103,6 +110,32 @@ return 0; }
+static int parse_wp_range(unsigned int *start, unsigned int *len) +{ + char *endptr = NULL, *token = NULL; + + if (!optarg) { + msg_gerr("Error: No wp-range values provided\n"); + return -1; + } + + token = strtok(optarg, ","); + if (!token) { + msg_gerr("Error: Invalid wp-range argument format\n"); + return -1; + } + *start = strtoul(token, &endptr, 0); + + token = strtok(NULL, ","); + if (!token) { + msg_gerr("Error: Invalid wp-range argument format\n"); + return -1; + } + *len = strtoul(token, &endptr, 0); + + return 0; +} + int main(int argc, char *argv[]) { const struct flashchip *chip = NULL; @@ -116,6 +149,8 @@ int list_supported_wiki = 0; #endif int flash_name = 0, flash_size = 0; + int set_wp_enable = 0, set_wp_disable = 0, wp_status = 0; + int set_wp_range = 0, set_wp_region = 0, wp_list = 0; int read_it = 0, write_it = 0, erase_it = 0, verify_it = 0; int dont_verify_it = 0, dont_verify_all = 0, list_supported = 0, operation_specified = 0; struct flashrom_layout *layout = NULL; @@ -127,8 +162,15 @@ OPTION_FLASH_CONTENTS, OPTION_FLASH_NAME, OPTION_FLASH_SIZE, + OPTION_WP_STATUS, + OPTION_WP_SET_RANGE, + OPTION_WP_SET_REGION, + OPTION_WP_ENABLE, + OPTION_WP_DISABLE, + OPTION_WP_LIST, }; int ret = 0; + unsigned int wp_start = 0, wp_len = 0;
static const char optstring[] = "r:Rw:v:nNVEfc:l:i:p:Lzho:"; static const struct option long_options[] = { @@ -150,6 +192,12 @@ {"flash-name", 0, NULL, OPTION_FLASH_NAME}, {"flash-size", 0, NULL, OPTION_FLASH_SIZE}, {"get-size", 0, NULL, OPTION_FLASH_SIZE}, // (deprecated): back compatibility. + {"wp-status", 0, 0, OPTION_WP_STATUS}, + {"wp-range", required_argument, NULL, OPTION_WP_SET_RANGE}, + {"wp-region", 1, 0, OPTION_WP_SET_REGION}, + {"wp-enable", optional_argument, 0, OPTION_WP_ENABLE}, + {"wp-disable", 0, 0, OPTION_WP_DISABLE}, + {"wp-list", 0, 0, OPTION_WP_LIST}, {"list-supported", 0, NULL, 'L'}, {"list-supported-wiki", 0, NULL, 'z'}, {"programmer", 1, NULL, 'p'}, @@ -169,6 +217,7 @@ char *tempstr = NULL; char *pparam = NULL; struct layout_include_args *include_args = NULL; + char *wp_mode_opt = NULL;
flashrom_set_log_callback((flashrom_log_callback *)&flashrom_print_cb);
@@ -286,6 +335,26 @@ cli_classic_validate_singleop(&operation_specified); flash_size = 1; break; + case OPTION_WP_STATUS: + wp_status = 1; + break; + case OPTION_WP_LIST: + wp_list = 1; + break; + case OPTION_WP_SET_RANGE: + if (parse_wp_range(&wp_start, &wp_len) < 0) + cli_classic_abort_usage("Incorrect wp-range arguments provided.\n"); + + set_wp_range = 1; + break; + case OPTION_WP_ENABLE: + set_wp_enable = 1; + if (optarg) + wp_mode_opt = strdup(optarg); + break; + case OPTION_WP_DISABLE: + set_wp_disable = 1; + break; case 'L': cli_classic_validate_singleop(&operation_specified); list_supported = 1; @@ -565,11 +634,32 @@ goto out_shutdown; }
- if (!(read_it | write_it | verify_it | erase_it | flash_name | flash_size)) { + if (!(read_it | write_it | verify_it | erase_it | flash_name | flash_size + | set_wp_range | set_wp_region | set_wp_enable | + set_wp_disable | wp_status | wp_list)) { msg_ginfo("No operations were specified.\n"); goto out_shutdown; }
+ if (set_wp_enable && set_wp_disable) { + msg_ginfo("Error: --wp-enable and --wp-disable are mutually exclusive\n"); + ret = 1; + goto out_shutdown; + } + if (set_wp_range && set_wp_region) { + msg_gerr("Error: Cannot use both --wp-range and --wp-region simultaneously.\n"); + ret = 1; + goto out_shutdown; + } + + if (set_wp_range || set_wp_region) { + if (!fill_flash->chip->wp || !fill_flash->chip->wp->set_range) { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + if (flash_name) { if (fill_flash->chip->vendor && fill_flash->chip->name) { printf("vendor="%s" name="%s"\n", @@ -586,6 +676,66 @@ goto out_shutdown; }
+ if (wp_status) { + if (fill_flash->chip->wp && fill_flash->chip->wp->wp_status) { + ret |= fill_flash->chip->wp->wp_status(fill_flash); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + } + goto out_shutdown; + } + + /* Note: set_wp_disable should be done before setting the range */ + if (set_wp_disable) { + if (fill_flash->chip->wp && fill_flash->chip->wp->disable) { + ret |= fill_flash->chip->wp->disable(fill_flash); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + + if (!ret && set_wp_enable) { + enum wp_mode wp_mode; + + if (wp_mode_opt) + wp_mode = get_wp_mode(wp_mode_opt); + else + wp_mode = WP_MODE_HARDWARE; /* default */ + + if (wp_mode == WP_MODE_UNKNOWN) { + msg_gerr("Error: Invalid WP mode: "%s"\n", wp_mode_opt); + ret = 1; + goto out_shutdown; + } + + if (fill_flash->chip->wp && fill_flash->chip->wp->enable) { + ret |= fill_flash->chip->wp->enable(fill_flash, wp_mode); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + + if (wp_list) { + msg_ginfo("Valid write protection ranges:\n"); + if (fill_flash->chip->wp && fill_flash->chip->wp->list_ranges) { + ret |= fill_flash->chip->wp->list_ranges(fill_flash); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + } + goto out_shutdown; + } + + /* Note: set_wp_range must happen before set_wp_enable */ + if (set_wp_range) { + ret |= fill_flash->chip->wp->set_range(fill_flash, wp_start, wp_len); + } + if (layoutfile) { layout = get_global_layout(); } else if (ifd && (flashrom_layout_read_from_ifd(&layout, fill_flash, NULL, 0) || diff --git a/flash.h b/flash.h index 2f0143b..fefca9d 100644 --- a/flash.h +++ b/flash.h @@ -235,6 +235,8 @@ int (*unlock) (struct flashctx *flash); int (*write) (struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int (*read) (struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len); + uint8_t (*read_status) (const struct flashctx *flash); + int (*write_status) (const struct flashctx *flash, int status); struct voltage { uint16_t min; uint16_t max; @@ -243,6 +245,8 @@
/* SPI specific options (TODO: Make it a union in case other bustypes get specific options.) */ uint8_t wrea_override; /**< override opcode for write extended address register */ + + struct wp *wp; };
struct flashrom_flashctx { diff --git a/meson.build b/meson.build index 9836194..8418d72 100644 --- a/meson.build +++ b/meson.build @@ -366,6 +366,7 @@ srcs += 'udelay.c' srcs += 'w29ee011.c' srcs += 'w39.c' +srcs += 'writeprotect.c'
mapfile = 'libflashrom.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) diff --git a/writeprotect.c b/writeprotect.c new file mode 100644 index 0000000..b26c121 --- /dev/null +++ b/writeprotect.c @@ -0,0 +1,394 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <strings.h> + +#include "flash.h" +#include "flashchips.h" +#include "chipdrivers.h" +#include "spi.h" +#include "writeprotect.h" + +/* + * The following procedures rely on look-up tables to match the user-specified + * range with the chip's supported ranges. This turned out to be the most + * elegant approach since diferent flash chips use different levels of + * granularity and methods to determine protected ranges. In other words, + * be stupid and simple since clever arithmetic will not work for many chips. + */ + +struct wp_range { + unsigned int start; /* starting address */ + unsigned int len; /* len */ +}; + +enum bit_state { + OFF = 0, + ON = 1, + X = -1 /* don't care. Must be bigger than max # of bp. */ +}; + +/* + * Generic write-protection schema for 25-series SPI flash chips. This assumes + * there is a status register that contains one or more consecutive bits which + * determine which address range is protected. + */ + +struct status_register_layout { + int bp0_pos; /* position of BP0 */ + int bp_bits; /* number of block protect bits */ + int srp_pos; /* position of status register protect enable bit */ +}; + +/* + * The following ranges and functions are useful for representing the + * writeprotect schema in which there are typically 5 bits of + * relevant information stored in status register 1: + * m.sec: This bit indicates the units (sectors vs. blocks) + * m.tb: The top-bottom bit indicates if the affected range is at the top of + * the flash memory's address space or at the bottom. + * bp: Bitmask representing the number of affected sectors/blocks. + */ +struct wp_range_descriptor { + struct modifier_bits m; + unsigned int bp; /* block protect bitfield */ + struct wp_range range; +}; + +struct wp_context { + struct status_register_layout sr1; /* status register 1 */ + struct wp_range_descriptor *descrs; + + /* + * Some chips store modifier bits in one or more special control + * registers instead of the status register like many older SPI NOR + * flash chips did. get_modifier_bits() and set_modifier_bits() will do + * any chip-specific operations necessary to get/set these bit values. + */ + int (*get_modifier_bits)(const struct flashctx *flash, + struct modifier_bits *m); + int (*set_modifier_bits)(const struct flashctx *flash, + struct modifier_bits *m); +}; + +/* + * Mask to extract write-protect enable and range bits + * Status register 1: + * SRP0: bit 7 + * range(BP2-BP0): bit 4-2 + * range(BP3-BP0): bit 5-2 (large chips) + * Status register 2: + * SRP1: bit 1 + */ +#define MASK_WP_AREA (0x9C) +#define MASK_WP_AREA_LARGE (0x9C) +#define MASK_WP2_AREA (0x01) + +static uint8_t do_read_status(const struct flashctx *flash) +{ + if (flash->chip->read_status) + return flash->chip->read_status(flash); + else + return spi_read_status_register(flash); +} + +static int do_write_status(const struct flashctx *flash, int status) +{ + if (flash->chip->write_status) + return flash->chip->write_status(flash, status); + else + return spi_write_status_register(flash, status); +} + +enum wp_mode get_wp_mode(const char *mode_str) +{ + enum wp_mode wp_mode = WP_MODE_UNKNOWN; + + if (!strcasecmp(mode_str, "hardware")) + wp_mode = WP_MODE_HARDWARE; + else if (!strcasecmp(mode_str, "power_cycle")) + wp_mode = WP_MODE_POWER_CYCLE; + else if (!strcasecmp(mode_str, "permanent")) + wp_mode = WP_MODE_PERMANENT; + + return wp_mode; +} + +/* Given a flash chip, this function returns its writeprotect info. */ +static int generic_range_table(const struct flashctx *flash, + struct wp_context **wp, + int *num_entries) +{ + *wp = NULL; + *num_entries = 0; + + switch (flash->chip->manufacture_id) { + default: + msg_cerr("%s: flash vendor (0x%x) not found, aborting\n", + __func__, flash->chip->manufacture_id); + return -1; + } + + return 0; +} + +static uint8_t generic_get_bp_mask(struct wp_context *wp) +{ + return ((1 << (wp->sr1.bp0_pos + wp->sr1.bp_bits)) - 1) ^ \ + ((1 << wp->sr1.bp0_pos) - 1); +} + +static uint8_t generic_get_status_check_mask(struct wp_context *wp) +{ + return generic_get_bp_mask(wp) | 1 << wp->sr1.srp_pos; +} + +/* Given a [start, len], this function finds a block protect bit combination + * (if possible) and sets the corresponding bits in "status". Remaining bits + * are preserved. */ +static int generic_range_to_status(const struct flashctx *flash, + unsigned int start, unsigned int len, + uint8_t *status, uint8_t *check_mask) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int i, range_found = 0, num_entries; + uint8_t bp_mask; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + bp_mask = generic_get_bp_mask(wp); + + for (i = 0, r = &wp->descrs[0]; i < num_entries; i++, r++) { + msg_cspew("comparing range 0x%x 0x%x / 0x%x 0x%x\n", + start, len, r->range.start, r->range.len); + if ((start == r->range.start) && (len == r->range.len)) { + *status &= ~(bp_mask); + *status |= r->bp << (wp->sr1.bp0_pos); + + if (wp->set_modifier_bits) { + if (wp->set_modifier_bits(flash, &r->m) < 0) { + msg_cerr("error setting modifier bits for range.\n"); + return -1; + } + } + + range_found = 1; + break; + } + } + + if (!range_found) { + msg_cerr("%s: matching range not found\n", __func__); + return -1; + } + + *check_mask = generic_get_status_check_mask(wp); + return 0; +} + +static int generic_status_to_range(const struct flashctx *flash, + const uint8_t sr1, unsigned int *start, unsigned int *len) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int num_entries, i, status_found = 0; + uint8_t sr1_bp; + struct modifier_bits m; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + /* modifier bits may be compared more than once, so get them here */ + if (wp->get_modifier_bits && wp->get_modifier_bits(flash, &m) < 0) + return -1; + + sr1_bp = (sr1 >> wp->sr1.bp0_pos) & ((1 << wp->sr1.bp_bits) - 1); + + for (i = 0, r = &wp->descrs[0]; i < num_entries; i++, r++) { + if (wp->get_modifier_bits) { + if (memcmp(&m, &r->m, sizeof(m))) + continue; + } + msg_cspew("comparing 0x%02x 0x%02x\n", sr1_bp, r->bp); + if (sr1_bp == r->bp) { + *start = r->range.start; + *len = r->range.len; + status_found = 1; + break; + } + } + + if (!status_found) { + msg_cerr("matching status not found\n"); + return -1; + } + + return 0; +} + +/* Given a [start, len], this function calls generic_range_to_status() to + * convert it to flash-chip-specific range bits, then sets into status register. + */ +static int generic_set_range(const struct flashctx *flash, + unsigned int start, unsigned int len) +{ + uint8_t status, expected, check_mask; + + status = do_read_status(flash); + msg_cdbg("%s: old status: 0x%02x\n", __func__, status); + + expected = status; /* preserve non-bp bits */ + if (generic_range_to_status(flash, start, len, &expected, &check_mask)) + return -1; + + do_write_status(flash, expected); + + status = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, status); + if ((status & check_mask) != (expected & check_mask)) { + msg_cerr("expected=0x%02x, but actual=0x%02x. check mask=0x%02x\n", + expected, status, check_mask); + return 1; + } + return 0; +} + +/* Set/clear the status regsiter write protect bit in SR1. */ +static int generic_set_srp0(const struct flashctx *flash, int enable) +{ + uint8_t status, expected, check_mask; + struct wp_context *wp; + int num_entries; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + expected = do_read_status(flash); + msg_cdbg("%s: old status: 0x%02x\n", __func__, expected); + + if (enable) + expected |= 1 << wp->sr1.srp_pos; + else + expected &= ~(1 << wp->sr1.srp_pos); + + do_write_status(flash, expected); + + status = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, status); + + check_mask = generic_get_status_check_mask(wp); + msg_cdbg("%s: check mask: 0x%02x\n", __func__, check_mask); + if ((status & check_mask) != (expected & check_mask)) { + msg_cerr("expected=0x%02x, but actual=0x%02x. check mask=0x%02x\n", + expected, status, check_mask); + return -1; + } + + return 0; +} + +static int generic_enable_writeprotect(const struct flashctx *flash, + enum wp_mode wp_mode) +{ + int ret; + + if (wp_mode != WP_MODE_HARDWARE) { + msg_cerr("%s(): unsupported write-protect mode\n", __func__); + return 1; + } + + ret = generic_set_srp0(flash, 1); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int generic_disable_writeprotect(const struct flashctx *flash) +{ + int ret; + + ret = generic_set_srp0(flash, 0); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int generic_list_ranges(const struct flashctx *flash) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int i, num_entries; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + r = &wp->descrs[0]; + for (i = 0; i < num_entries; i++) { + msg_cinfo("start: 0x%06x, length: 0x%06x\n", + r->range.start, r->range.len); + r++; + } + + return 0; +} + +static int wp_context_status(const struct flashctx *flash) +{ + uint8_t sr1; + unsigned int start, len; + int ret = 0; + struct wp_context *wp; + int num_entries, wp_en; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + sr1 = do_read_status(flash); + wp_en = (sr1 >> wp->sr1.srp_pos) & 1; + + msg_cinfo("WP: status: 0x%04x\n", sr1); + msg_cinfo("WP: status.srp0: %x\n", wp_en); + /* FIXME: SRP1 is not really generic, but we probably should print + * it anyway to have consistent output. #legacycruft */ + msg_cinfo("WP: status.srp1: %x\n", 0); + msg_cinfo("WP: write protect is %s.\n", + wp_en ? "enabled" : "disabled"); + + msg_cinfo("WP: write protect range: "); + if (generic_status_to_range(flash, sr1, &start, &len)) { + msg_cinfo("(cannot resolve the range)\n"); + ret = -1; + } else { + msg_cinfo("start=0x%08x, len=0x%08x\n", start, len); + } + + return ret; +} + +struct wp wp_generic = { + .list_ranges = generic_list_ranges, + .set_range = generic_set_range, + .enable = generic_enable_writeprotect, + .disable = generic_disable_writeprotect, + .wp_status = wp_context_status, +}; diff --git a/writeprotect.h b/writeprotect.h new file mode 100644 index 0000000..bded4c8 --- /dev/null +++ b/writeprotect.h @@ -0,0 +1,51 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __WRITEPROTECT_H__ +#define __WRITEPROTECT_H__ 1 + +enum wp_mode { + WP_MODE_UNKNOWN = -1, + WP_MODE_HARDWARE, /* hardware WP pin determines status */ + WP_MODE_POWER_CYCLE, /* WP active until power off/on cycle */ + WP_MODE_PERMANENT, /* status register permanently locked, + WP permanently enabled */ +}; + +struct wp { + int (*list_ranges)(const struct flashctx *flash); + int (*set_range)(const struct flashctx *flash, + unsigned int start, unsigned int len); + int (*enable)(const struct flashctx *flash, enum wp_mode mode); + int (*disable)(const struct flashctx *flash); + int (*wp_status)(const struct flashctx *flash); +}; + +extern struct wp wp_generic; + +enum wp_mode get_wp_mode(const char *mode_str); + +/* + * Generic write-protect stuff + */ + +struct modifier_bits { + int sec; /* if 1, bp bits describe sectors */ + int tb; /* value of top/bottom select bit */ +}; + +#endif /* !__WRITEPROTECT_H__ */
Nico Huber has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 12:
(1 comment)
Patchset:
PS12:
Did somebody look into Hatim's patches [1]? I didn't but feel like we owe them some attention. They cover more than just write protection. I fear, if we don't look into them now, we never will.
[1] Somewhere amidst https://patchwork.coreboot.org/project/flashrom/list/
I looked into some of them way back when they were first written, but wasn't doing much work with flashrom at the time so I didn't get involved much. IIRC the only issue I had was that some patches were too generalized, i.e. they made assumptions that were true for some chips but not all. Some large Spansion S25FS and FL chips were particularly odd, and there were cases where "don't care" block protect bits could impact generic methods unexpectedly.
Still, he did a lot of good work and it may be useful to revisit those patches to see if we can salvage some of them. And as you mention they cover more than write protection, there might be some OTP-related patches we can use.
After looking at them for a bit I think if we can first work out how to integrate this relatively self-contained support that actually working in the field we can massage forwards into the more generalised version on the list. Otherwise we are forever stuck between a working self-contained abeit not ideal support or a idealised support that isn't completed yet. Since there is a reasonable amount of work to fully get to the end of the road there we need to lay some ground work down.
"Working" is no argument for an upstream open-source project. "Maintainable" would get things going. It's been half a year now and you got 1 odd patch merged. Do you really think this is the faster way? I've been looking ahead and the next patch completely ignores the abstraction added by this one. It's a mess and exactly the kind of patches that slow things down, short, mid and long term. Who is faster on the top of a moun- tain, the one who takes the long winding road or the one who starts climbing straight ahead and keeps falling down?
Please stop this code-porting folly and port the features that chromium flashrom has and upstream lacks instead. If necessary, rewriting those features together with the community. Even after years wasted, I think that's still the faster way.
(And don't forget that much upstream work has stalled during these years waiting for your upstreaming to finish. What you have achieved so far is that this "forever stuck" has extended from chromium to upstream.)
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/40325 )
Change subject: Add writeprotect support infrastructure ......................................................................
Patch Set 12:
(1 comment)
Patchset:
PS12:
Did somebody look into Hatim's patches [1]? I didn't but feel like […]
The slow down here is because the cros implementation (which I agree is very poor) is being rewritten to line more up with the patches pointed out on the mailing list to come up with something to keep moving forwards with. I believe that was the desire of the community given the above so that is what I am working towards.
UPDATE: The immediate thing at the moment that is happening in our tree is to remove the `.wp` field out of flashchip struct and replace it with a lookup table in writeprotect.c this is to make writeprotect more self-contained. Also, the other part is is breaking it up a little just as Angel pointed out but we have to unroll a few problems before that with ichspi.
BACKGROUND: I don't think anything is actually stuck here just hard to resource as there isn't really a team of people working on it, just me trying to pull the show together largely on my own. I am working on mentoring others to help grow the participation however that takes its own time and effort to. These are things beyond my control so I can only do my personal best to focus efforts and bring attention to help things move forwards.
ISSUES: Some other background things add time to this such as https://review.coreboot.org/c/flashrom/+/45748 which is a bit of a mess due to the non-standardness of these opcodes. I have been trying to talk to some of these companies and get them onboard to helping chip-level support in Flashrom directly to some success, https://github.com/flashrom/flashrom/commit/32f4cb4ffa2854354f00e5facc9ccb8c... its all just super hard to bring so many things together on my own and this is fundamentally the limiting factor.
REASONING: As for the writeprotect support I have to figure out how to modify it to gel more into upstream piece by piece while maintaining support for all the shipped hardware without breaking it. The current code has zero tests apart from the AVL tooling which only gets run once for when the chip is first qualified. The framework at least allows the chip AVL qualification tool to be run using the upstream Flashrom so we can have XMC or similar working directly here and not in our tree.
Hope this helps shed some light and thanks for keeping a eye on this as well!