- struct flashchip contains pointer to a struct wp (for allowing flexibility of reuse), which in turn has members to represent protection ranges and, pointers to functions to fetch bp_bitmask, set protection range, disable any protection, and print protection range table. Function pointers allow flexibility to assign chip specific routines for exotic cases. - Print table of valid ranges for write protection - Automatically fetches bit names from status_register->layout to make list more meaningful - Disabling of block protection is unified (we can decommission the various spi_disable_blockprotect() functions from spi25_statusreg.c) - Write protection mode(s) of status register(s) are automatically queried and disabled (courtesy of struct status_register) - BP bitmasks are fetched from wp->bitmask - For around 50% of chips supported by flashrom (as of writing this), ranges are automatically generated based. This is especially true of GigaDevice and Winbond chips. For many other chips the generated range can be used as boilerplate to start with. - The presence of a CMP bit is automatically handled for printing as well as setting range (as long as a standard is followed - please refer comment under compute_cmp_ranges label in sec_block_range_pattern() function in writeprotect.c)
Signed-off-by: Hatim Kanchwala hatim@hatimak.me --- Makefile | 2 +- chipdrivers.h | 14 ++ flash.h | 23 +++- spi25_statusreg.c | 6 + writeprotect.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ writeprotect.h | 33 +++++ 6 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 writeprotect.c create mode 100644 writeprotect.h
diff --git a/Makefile b/Makefile index 4ebde1e..de08492 100644 --- a/Makefile +++ b/Makefile @@ -504,27 +504,27 @@ endif ifeq ($(CONFIG_IT8212), yes) UNSUPPORTED_FEATURES += CONFIG_IT8212=yes else override CONFIG_IT8212 = no endif endif
############################################################################### # Flash chip drivers and bus support infrastructure.
CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \ sst28sf040.o 82802ab.o \ sst49lfxxxc.o sst_fwhub.o flashchips.o spi.o spi25.o spi25_statusreg.o \ - opaque.o sfdp.o en29lv640b.o at45db.o + writeprotect.o opaque.o sfdp.o en29lv640b.o at45db.o
############################################################################### # Library code.
LIB_OBJS = layout.o flashrom.o udelay.o programmer.o helpers.o
############################################################################### # Frontend related stuff.
CLI_OBJS = cli_classic.o cli_output.o cli_common.o print.o
# Set the flashrom version string from the highest revision number of the checked out flashrom files. # Note to packagers: Any tree exported with "make export" or "make tarball" diff --git a/chipdrivers.h b/chipdrivers.h index 4c58f30..c60aff8 100644 --- a/chipdrivers.h +++ b/chipdrivers.h @@ -17,26 +17,27 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * Header file for flash chip drivers. Included from flash.h. * As a general rule, every function listed here should take a pointer to * struct flashctx as first parameter. */
#ifndef __CHIPDRIVERS_H__ #define __CHIPDRIVERS_H__ 1
#include "flash.h" /* for chipaddr and flashctx */ #include "spi25_statusreg.h" /* For enum status_register_num */ +#include "writeprotect.h"
/* spi.c */ int spi_aai_write(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int spi_chip_write_256(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int spi_chip_read(struct flashctx *flash, uint8_t *buf, unsigned int start, int unsigned len);
/* spi25.c */ int probe_spi_rdid(struct flashctx *flash); int probe_spi_rdid4(struct flashctx *flash); int probe_spi_rems(struct flashctx *flash); int probe_spi_res1(struct flashctx *flash); int probe_spi_res2(struct flashctx *flash); int probe_spi_res3(struct flashctx *flash); @@ -103,26 +104,39 @@ int spi_disable_blockprotect_at25f(struct flashctx *flash); int spi_disable_blockprotect_at25f512a(struct flashctx *flash); int spi_disable_blockprotect_at25f512b(struct flashctx *flash); int spi_disable_blockprotect_at25fs010(struct flashctx *flash); int spi_disable_blockprotect_at25fs040(struct flashctx *flash); int spi_prettyprint_status_register_en25s_wp(struct flashctx *flash); int spi_prettyprint_status_register_n25q(struct flashctx *flash); int spi_disable_blockprotect_n25q(struct flashctx *flash); int spi_prettyprint_status_register_bp2_ep_srwd(struct flashctx *flash); int spi_disable_blockprotect_bp2_ep_srwd(struct flashctx *flash); int spi_prettyprint_status_register_sst25(struct flashctx *flash); int spi_prettyprint_status_register_sst25vf016(struct flashctx *flash); int spi_prettyprint_status_register_sst25vf040b(struct flashctx *flash);
+/* writeprotect.c */ +struct range *sec_block_range_pattern(struct flashctx *flash); +char get_cmp(struct flashctx *flash); +int set_cmp(struct flashctx *flash, uint8_t cmp); +uint32_t bp_bitmask_generic(struct flashctx *flash); +struct range *bp_to_range(struct flashctx *flash, unsigned char bp_config); +int range_to_bp_bitfield(struct flashctx *flash, uint32_t start, uint32_t len); +int print_range_generic(struct flashctx *flash); +int print_table_generic(struct flashctx *flash); +int set_range_generic(struct flashctx *flash, uint32_t start, uint32_t len); +int disable_generic(struct flashctx *flash); +struct range *range_table_global(struct flashctx *flash); + /* sfdp.c */ int probe_spi_sfdp(struct flashctx *flash);
/* opaque.c */ int probe_opaque(struct flashctx *flash); int read_opaque(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len); int write_opaque(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int erase_opaque(struct flashctx *flash, unsigned int blockaddr, unsigned int blocklen);
/* at45db.c */ int probe_spi_at45db(struct flashctx *flash); int spi_prettyprint_status_register_at45db(struct flashctx *flash); int spi_disable_blockprotect_at45db(struct flashctx *flash); diff --git a/flash.h b/flash.h index 566b709..631c1a8 100644 --- a/flash.h +++ b/flash.h @@ -28,26 +28,27 @@
#include <inttypes.h> #include <stdio.h> #include <stdint.h> #include <stddef.h> #include <stdbool.h> #if IS_WINDOWS #include <windows.h> #undef min #undef max #endif
#include "spi25_statusreg.h" +#include "writeprotect.h"
#define ERROR_PTR ((void*)-1)
/* Error codes */ #define ERROR_OOM -100 #define TIMEOUT_ERROR -101
/* TODO: check using code for correct usage of types */ typedef uintptr_t chipaddr; #define PRIxPTR_WIDTH ((int)(sizeof(uintptr_t)*2))
/* Types and macros regarding the maximum flash space size supported by generic code. */ typedef uint32_t chipoff_t; /* Able to store any addressable offset within a supported flash memory. */ @@ -211,34 +212,54 @@ struct flashchip { uint8_t (*read) (struct flashctx *flash, enum status_register_num SRn); /* Set value of status register SRn to status. */ int (*write) (struct flashctx *flash, enum status_register_num SRn, uint8_t status); /* Print the contents of status register SRn. */ int (*print) (struct flashctx *flash, enum status_register_num SRn); /* Get mode of write protection currently in effect against status register. */ enum wp_mode (*get_wp_mode) (struct flashctx *flash); /* Set mode of write protection against status register. */ int (*set_wp_mode) (struct flashctx *flash, enum wp_mode wp_mode); int (*print_wp_mode) (struct flashctx *flash); } *status_register;
int (*printlock) (struct flashctx *flash); // TODO(hatim): This member should be decommissioned - int (*unlock) (struct flashctx *flash); + int (*unlock) (struct flashctx *flash); // TODO(hatim): This member should be decommissioned 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); struct voltage { uint16_t min; uint16_t max; } voltage; enum write_granularity gran; + + struct wp { + /* WP range table, indexed by BP bit configuration. For chips + * that also have a CMP bit, the most significant bit after + * the highest BP bit should represent the CMP bit. For example, + * a chip with BP[0..2] and CMP bits, the index mask is 0x0f and + * the most significant 1 represents CMP. */ + struct range *ranges; + /* Either ranges is assigned to or range_table is, NOT both. */ + /* Return pointer to WP range table. */ + struct range *(*range_table) (struct flashctx *flash); + /* Return BP(BP0, BP1, ... , SEC, TB) bit mask. */ + uint32_t (*bp_bitmask) (struct flashctx *flash); + /* Given a range, set the corresponding BP and CMP bit (if present) in the status + * register. If range is invalid, return -1 and abort writing to status register. */ + int (*set_range) (struct flashctx *flash, uint32_t start, uint32_t len); + /* Disable any block protection in effect. */ + int (*disable) (struct flashctx *flash); + int (*print_table) (struct flashctx *flash); + } *wp; };
struct flashctx { struct flashchip *chip; /* FIXME: The memory mappings should be saved in a more structured way. */ /* The physical_* fields store the respective addresses in the physical address space of the CPU. */ uintptr_t physical_memory; /* The virtual_* fields store where the respective physical address is mapped into flashrom's address * space. A value equivalent to (chipaddr)ERROR_PTR indicates an invalid mapping (or none at all). */ chipaddr virtual_memory; /* Some flash devices have an additional register space; semantics are like above. */ uintptr_t physical_registers; chipaddr virtual_registers; diff --git a/spi25_statusreg.c b/spi25_statusreg.c index ac77c1d..c1e0cd9 100644 --- a/spi25_statusreg.c +++ b/spi25_statusreg.c @@ -471,26 +471,27 @@ int set_wp_mode_generic(struct flashctx *flash, enum wp_mode wp_mode) * they never had been engaged: * If the lock bits are out of the way try to disable engaged protections. * To support uncommon global unprotects (e.g. on most AT2[56]xx1(A)) unprotect_mask can be used to force * bits to 0 additionally to those set in bp_mask and lock_mask. Only bits set in unprotect_mask are potentially * preserved when doing the final unprotect. * * To sum up: * bp_mask: set those bits that correspond to the bits in the status register that indicate an active protection * (which should be unset after this function returns). * lock_mask: set the bits that correspond to the bits that lock changing the bits above. * wp_mask: set the bits that correspond to bits indicating non-software revocable protections. * unprotect_mask: set the bits that should be preserved if possible when unprotecting. */ +// TODO(hatim): This function should be decommissioned once integration is complete static int spi_disable_blockprotect_generic(struct flashctx *flash, uint8_t bp_mask, uint8_t lock_mask, uint8_t wp_mask, uint8_t unprotect_mask) { uint8_t status; int result;
status = spi_read_status_register(flash); if ((status & bp_mask) == 0) { msg_cdbg2("Block protection is disabled.\n"); return 0; }
msg_cdbg("Some block protection in effect, disabling... "); if ((status & lock_mask) != 0) { @@ -519,55 +520,60 @@ static int spi_disable_blockprotect_generic(struct flashctx *flash, uint8_t bp_m return result; } status = spi_read_status_register(flash); if ((status & bp_mask) != 0) { msg_cerr("Block protection could not be disabled!\n"); flash->chip->printlock(flash); return 1; } msg_cdbg("disabled.\n"); return 0; }
/* A common block protection disable that tries to unset the status register bits masked by 0x3C. */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_disable_blockprotect(struct flashctx *flash) { return spi_disable_blockprotect_generic(flash, 0x3C, 0, 0, 0xFF); }
/* A common block protection disable that tries to unset the status register bits masked by 0x0C (BP0-1) and * protected/locked by bit #7. Useful when bits 4-5 may be non-0). */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_disable_blockprotect_bp1_srwd(struct flashctx *flash) { return spi_disable_blockprotect_generic(flash, 0x0C, 1 << 7, 0, 0xFF); }
/* A common block protection disable that tries to unset the status register bits masked by 0x1C (BP0-2) and * protected/locked by bit #7. Useful when bit #5 is neither a protection bit nor reserved (and hence possibly * non-0). */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_disable_blockprotect_bp2_srwd(struct flashctx *flash) { return spi_disable_blockprotect_generic(flash, 0x1C, 1 << 7, 0, 0xFF); }
/* A common block protection disable that tries to unset the status register bits masked by 0x3C (BP0-3) and * protected/locked by bit #7. */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_disable_blockprotect_bp3_srwd(struct flashctx *flash) { return spi_disable_blockprotect_generic(flash, 0x3C, 1 << 7, 0, 0xFF); }
/* A common block protection disable that tries to unset the status register bits masked by 0x7C (BP0-4) and * protected/locked by bit #7. */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_disable_blockprotect_bp4_srwd(struct flashctx *flash) { return spi_disable_blockprotect_generic(flash, 0x7C, 1 << 7, 0, 0xFF); }
// TODO(hatim): This function should be decommissioned once integration is complete static void spi_prettyprint_status_register_hex(uint8_t status) { msg_cdbg("Chip status register is 0x%02x.\n", status); }
/* Common highest bit: Status Register Write Disable (SRWD) or Status Register Protect (SRP). */ // TODO(hatim): This function should be decommissioned once integration is complete diff --git a/writeprotect.c b/writeprotect.c new file mode 100644 index 0000000..e256ae4 --- /dev/null +++ b/writeprotect.c @@ -0,0 +1,386 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2016 Hatim Kanchwala hatim@hatimak.me + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "chipdrivers.h" +#include "flash.h" +#include "spi25_statusreg.h" +#include "writeprotect.h" + +static uint32_t wp_range_table_id; +static struct range *wp_range_table; + +// TODO(hatim): Update for chips with density greater than 128Mb +static uint16_t const sec_block_multiples[7][8] = { + /* Chip densities */ + { 0, 1, 1, 1, 0, 1, 1, 1 }, /* = 64 kB */ + { 0, 2, 1, 1, 0, 2, 1, 1 }, /* = 128 kB */ + { 0, 4, 2, 1, 0, 4, 2, 1 }, /* = 256 kB */ + { 0, 8, 4, 2, 1, 1, 1, 1 }, /* = 512 kB */ + { 0, 16, 8, 4, 2, 1, 1, 1 }, /* = 1024 kB */ + { 0, 32, 16, 8, 4, 2, 1, 1 }, /* = 2 * 1024 kB */ + { 0, 64, 32, 16, 8, 4, 2, 1 }, /* >= 4 * 1024 kB and <= 16 * 1024 kB */ +}; + +static char const *bit_string[3][32] = { + { "0\t0\t0", "0\t0\t1", "0\t1\t0", "0\t1\t1", "1\t0\t0", "1\t0\t1", "1\t1\t0", "1\t1\t1" }, + { "0\t0\t0\t0", "0\t0\t0\t1", "0\t0\t1\t0", "0\t0\t1\t1", "0\t1\t0\t0", "0\t1\t0\t1", "0\t1\t1\t0", + "0\t1\t1\t1", "1\t0\t0\t0", "1\t0\t0\t1", "1\t0\t1\t0", "1\t0\t1\t1", "1\t1\t0\t0", "1\t1\t0\t1", + "1\t1\t1\t0", "1\t1\t1\t1" }, + { "0\t0\t0\t0\t0", "0\t0\t0\t0\t1", "0\t0\t0\t1\t0", "0\t0\t0\t1\t1", "0\t0\t1\t0\t0", "0\t0\t1\t0\t1", + "0\t0\t1\t1\t0", "0\t0\t1\t1\t1", "0\t1\t0\t0\t0", "0\t1\t0\t0\t1", "0\t1\t0\t1\t0", "0\t1\t0\t1\t1", + "0\t1\t1\t0\t0", "0\t1\t1\t0\t1", "0\t1\t1\t1\t0", "0\t1\t1\t1\t1", "1\t0\t0\t0\t0", "1\t0\t0\t0\t1", + "1\t0\t0\t1\t0", "1\t0\t0\t1\t1", "1\t0\t1\t0\t0", "1\t0\t1\t0\t1", "1\t0\t1\t1\t0", "1\t0\t1\t1\t1", + "1\t1\t0\t0\t0", "1\t1\t0\t0\t1", "1\t1\t0\t1\t0", "1\t1\t0\t1\t1", "1\t1\t1\t0\t0", "1\t1\t1\t0\t1", + "1\t1\t1\t1\t0", "1\t1\t1\t1\t1" }, +}; + +/* === Generic functions === */ +/* Most SPI chips (especially GigaDeice and Winbond) follow a specific + * pattern for their block protection ranges. This pattern is a function + * of BP and other modifier bits, and chip density. Quite a few AMIC, Eon, + * Fudan and Macronix chips also somewhat follow this pattern. Given the status + * register layout of a chip, the following returns the range table according + * to the pattern relevant to the chip. The presence of a CMP bit is handled + * automatically. Please see the note below for the standard for CMP bit. + * + * When adding support for newer chips that have protection ranges similar + * to the chips already using this pattern, consider using this function + * to generate the boilerplate range, and then modifying it appropriately. */ +// TODO(hatim): Make static (?) +struct range *sec_block_range_pattern(struct flashctx *flash) { + /* Note that a couple of lines in the code below may seem missing, + * but we get away with not writing them because ranges[] is static. */ + static struct range ranges[LEN_RANGES]; + uint8_t convention = 1, multiple = 0, cmp_mask_pos = 3; + /* cmp_mask_pos points to the bit position for CMP, if the chip has one. */ + int tot_size = flash->chip->total_size; + + // TODO(hatim): Add support for chips with density greater than 128Mb + switch (tot_size) { + case 64: + multiple = 0; + break; + case 128: + multiple = 1; + break; + case 256: + multiple = 2; + break; + case 512: + multiple = 3; + break; + case 1024: + multiple = 4; + convention = 0; + break; + case 2 * 1024: + multiple = 5; + convention = 0; + break; + case 4 * 1024: + case 8 * 1024: + case 16 * 1024: + multiple = 6; + break; + } + /* It has been observed that roles of SEC and TB bits are identical + * to that of BP4 and BP3 bits respectively. + * (BP2=0, BP1=0, BP1=0) and (BP2=1, BP1=1, BP1=1) almost always + * correpsond to NONE and ALL protection range respectively. */ + /* (SEC=0, TB=0) Uppper */ + for (int i = 0; i < 8; i++) { + if (sec_block_multiples[multiple][i]) { + ranges[i].start = (tot_size - tot_size / sec_block_multiples[multiple][i]) * 1024; + ranges[i].len = tot_size / sec_block_multiples[multiple][i]; + } + } + /* No point in continuing if chip does NOT have TB (or BP3) bit. */ + if (!(pos_bit(flash, TB) != -1 || pos_bit(flash, BP3) != -1)) + goto compute_cmp_ranges; + + /* (SEC=0, TB=1) Lower */ + for (int i = 0; i < 8; i++) { + if (sec_block_multiples[multiple][i]) { + ranges[0x01 << 3 | i].len = tot_size / sec_block_multiples[multiple][i]; + } + } + cmp_mask_pos++; + /* No point in continuing if chip does NOT have SEC (or BP4) bit. */ + if (!(pos_bit(flash, SEC) != -1 || pos_bit(flash, BP4) != -1)) + goto compute_cmp_ranges; + + /* (SEC=1, TB=0) Uppper */ + ranges[0x02 << 3 | 0x01].start = (tot_size - 4) * 1024; + ranges[0x02 << 3 | 0x01].len = 4; + ranges[0x02 << 3 | 0x02].start = (tot_size - 8) * 1024; + ranges[0x02 << 3 | 0x02].len = 8; + ranges[0x02 << 3 | 0x03].start = (tot_size - 16) * 1024; + ranges[0x02 << 3 | 0x03].len = 16; + ranges[0x02 << 3 | 0x04].start = (tot_size - 32) * 1024; + ranges[0x02 << 3 | 0x04].len = 32; + ranges[0x02 << 3 | 0x05].start = (tot_size - 32) * 1024; + ranges[0x02 << 3 | 0x05].len = 32; + if (convention) { + ranges[0x02 << 3 | 0x06].start = (tot_size - 32) * 1024; + ranges[0x02 << 3 | 0x06].len = 32; + } else + ranges[0x02 << 3 | 0x06].len = tot_size; + ranges[0x02 << 3 | 0x07].len = tot_size; + + /* SEC, TB = (1, 1) Lower */ + ranges[0x03 << 3 | 0x01].len = 4; + ranges[0x03 << 3 | 0x02].len = 8; + ranges[0x03 << 3 | 0x03].len = 16; + ranges[0x03 << 3 | 0x04].len = 32; + ranges[0x03 << 3 | 0x05].len = 32; + if (convention) + ranges[0x02 << 3 | 0x06].len = 32; + else + ranges[0x02 << 3 | 0x06].len = tot_size; + ranges[0x03 << 3 | 0x07].len = tot_size; + cmp_mask_pos++; + +compute_cmp_ranges: + /* For chips with a CMP bit, the first unused bit position (or the position just higher + * than the most significant bit position in the bp_bitmask) is for the CMP bit. So for + * instance a chip has BP[2..0] and CMP bits then, bits 0, 1 and 2 represent BP0, BP1 + * and BP2 respectively, and bit 3 represents CMP. We use bp_bitmask to index into + * ranges[]. So, for CMP=1 and BP[2..0]=0x03, the valid range is given by ranges[0x0f]. */ + if (pos_bit(flash, CMP) != -1) { + for (int i = 0, j = 1 << cmp_mask_pos; i < (1 << cmp_mask_pos); i++) { + ranges[j | i].len = tot_size - ranges[i].len; + if (ranges[i].len != tot_size && ranges[i].start == 0x000000) + ranges[j | i].start = ranges[i].len * 1024; + } + } + + return ranges; +} + +// TODO(hatim): Make static (?) +char get_cmp(struct flashctx *flash) { + int pos_cmp = pos_bit(flash, CMP); + if (pos_cmp == -1) + return -1; + else { + enum status_register_num SRn = pos_cmp / 8; + pos_cmp -= SRn * 8; + uint8_t cmp_mask = 1 << pos_cmp; + uint8_t status = flash->chip->status_register->read(flash, SRn); + return status & cmp_mask; + } +} + +// TODO(hatim): Make static (?) +// TODO(hatim): Change type of cmp to enum bit_state +int set_cmp(struct flashctx *flash, uint8_t cmp) { + int pos_cmp = pos_bit(flash, CMP); + if (pos_cmp == -1) { + msg_cdbg("Chip does not have CMP bit!\n"); + msg_cerr("%s failed\n", __func__); + return -1; + } else { + enum status_register_num SRn = pos_cmp / 8; + pos_cmp -= SRn * 8; + uint8_t cmp_mask = 1 << pos_cmp; + uint8_t status = flash->chip->status_register->read(flash, SRn); + status = ((status & ~cmp_mask) | ((cmp ? 1 : 0) << pos_cmp)) & 0xff; + /* FIXME: Verify whether CMP was indeed written. */ + return flash->chip->status_register->write(flash, SRn, status); + } +} + +/* Wrapper for flash->chip->wp->range_table(). Returns the WP range table member of wp, + * or, generates (then stores for later use) and returns one using range_table member + * of wp. */ +struct range *range_table_global(struct flashctx *flash) { + if (flash->chip->wp->ranges) { + return flash->chip->wp->ranges; + } else { + if (wp_range_table_id != flash->chip->model_id) { + wp_range_table = flash->chip->wp->range_table(flash); + wp_range_table_id = flash->chip->model_id; + } + return wp_range_table; + } +} + +/* Return BP bit mask. */ +uint32_t bp_bitmask_generic(struct flashctx *flash) { + /* The following code relies on the assumption that BP bits are present + * in continuity (back-to-back) in the first status register. Correct + * bitmask will NOT be returned if, say TB bit, is separately located + * from the rest of the BP bits. */ + unsigned char bitmask = 0x00; + enum status_register_bit bits[] = { BP0, BP1, BP2, BP3, BP4, SEC, TB }; + + for (int i = 0; i < ARRAY_SIZE(bits); i++) { + if (pos_bit(flash, bits[i]) != -1) + bitmask |= 1 << pos_bit(flash, bits[i]); + } + return bitmask; +} + +/* Given BP bits' configuration, return the corresponding range. + * For chips with a CMP bit, read the CMP value and automatically adjust. */ +// TODO(hatim): Change type of bp_config to uint8_t +struct range *bp_to_range(struct flashctx *flash, unsigned char bp_config) { + if (pos_bit(flash, CMP) == -1) { + return &(range_table_global(flash)[bp_config]); + } else { + uint32_t cmp_mask = (flash->chip->wp->bp_bitmask(flash) >> pos_bit(flash, BP0)) + 1; + cmp_mask = (get_cmp(flash) ? 0xffff : 0x0000) & cmp_mask; + return &(range_table_global(flash)[cmp_mask | bp_config]); + } +} + +/* Given WP range, return BP bits' configuration (or -1, if range is invalid). + * FIXME(hatim): This does NOT handle CMP bit automatically (yet) */ +int range_to_bp_bitfield(struct flashctx *flash, uint32_t start, uint32_t len) { + struct range *table = range_table_global(flash); + int limit = 0, bitmask = flash->chip->wp->bp_bitmask(flash); + + for (int i = 7; i >= 0; i--) + if (bitmask & (1 << i)) + limit++; + limit = 1 << limit; + for (int i = 0; i < limit * ((pos_bit(flash, CMP) == -1) ? 1 : 2); i++) { + if (table[i].start == start && table[i].len == len) + return i; + } + return -1; +} + +int print_range_generic(struct flashctx *flash) { + uint8_t status = flash->chip->status_register->read(flash, SR1); + uint8_t bp_config = (status & flash->chip->wp->bp_bitmask(flash)) >> pos_bit(flash, BP0); + + struct range *range = bp_to_range(flash, bp_config); + + if (range->len == flash->chip->total_size) + msg_cdbg("Entire memory range is protected against erases and writes\n"); + else if (range->len != 0) + msg_cdbg("%d kB, starting from address 0x%06x, is protected against erases and writes\n", + range->len, range->start); + else + msg_cdbg("Entire memory range is open to erases and writes\n"); + return 0; +} + +int print_table_generic(struct flashctx *flash) { + uint32_t bitmask = flash->chip->wp->bp_bitmask(flash); + int limit = 0, pos_cmp = pos_bit(flash, CMP); + /* FIXME: The following relies on the assumption that the entire + * BP bitmask (BP0..4, SEC, TB) is spread across only the first + * status register, hence 8 iterations for the following loop. */ + if (pos_cmp != -1) + msg_cdbg("%s\t", statreg_bit_desc[CMP][0]); + for (int i = 7; i >= 0; i--) { + if (bitmask & (1 << i)) { + limit++; + msg_cdbg("%s\t", statreg_bit_desc[flash->chip->status_register->layout[SR1][i]][0]); + } + } + msg_cdbg("START ADDR\tLENGTH (kB)\n"); + + limit = 1 << limit; + uint8_t j = (limit == 32) ? 2 : (limit == 16) ? 1 : 0; + struct range *table = range_table_global(flash); + + for (int i = 0; i < limit * ((pos_cmp == -1) ? 1 : 2); i++) + msg_cdbg("%s%s\t0x%06x\t%6d\n", + (pos_cmp == -1) ? "" : (i >= limit) ? "1\t" : "0\t", + bit_string[j][(i >= limit) ? i - limit : i], + table[i].start, table[i].len); + return 0; +} + +/* Given a range, set the corresponding BP and CMP bit (if present) in the status + * register. If range is invalid, return -1 and abort writing to status register. */ +int set_range_generic(struct flashctx *flash, uint32_t start, uint32_t len) { + int bitfield = range_to_bp_bitfield(flash, start, len); + if (bitfield == -1) { + msg_cdbg("Invalid block protection range start=0x%06x, len=%d kB\n", start, len); + msg_cerr("%s failed\n", __func__); + return -1; + } + + int status = flash->chip->status_register->read(flash, SR1); + int bitmask = flash->chip->wp->bp_bitmask(flash), result; + + status = (((bitfield << pos_bit(flash, BP0)) & bitmask) | (status & ~bitmask)) & 0xff; + result = flash->chip->status_register->write(flash, SR1, (uint8_t)status); + if (pos_bit(flash, CMP) != -1) { + int cmp_mask = (flash->chip->wp->bp_bitmask(flash) >> pos_bit(flash, BP0)) + 1; + result &= set_cmp(flash, bitfield & cmp_mask); + } + if (result) + msg_cerr("%s failed\n", __func__); + // FIXME(hatim): Perform a read to verify BP bitfield + return result; +} + +/* TODO: This should eventually replace functionality of (almost all) variants of blockprotect + * from spi25_statusreg.c */ +int disable_generic(struct flashctx *flash) { + /* The following code assumes that all of the BP and modifier bits (SEC, TB) are present + * only in the first status register, and consequently the BP bitmask only masks bits + * from the first status register. */ + uint32_t bitmask = flash->chip->wp->bp_bitmask(flash); + uint8_t status_sr1 = flash->chip->status_register->read(flash, SR1); + int result; + if (!(status_sr1 & bitmask)) { + msg_cdbg("No block protection is in effect\n"); + return 0; + } + + msg_cdbg("Some block protection is in effect, trying to disable...\n"); + switch (flash->chip->status_register->get_wp_mode(flash)) { + case WP_MODE_HARDWARE_PROTECTED: + case WP_MODE_POWER_CYCLE: + case WP_MODE_PERMANENT: + msg_cdbg("Write protection mode for status register(s) disallows writing to it, disabling failed\n"); + msg_cerr("%s failed\n", __func__); + return 1; + case WP_MODE_INVALID: + msg_cdbg("Invalid write protection mode for status register(s), disabling failed\n"); + msg_cerr("%s failed\n", __func__); + return 1; + default: + break; + } + /* It is observed that usually unsetting BP[0..2] corresponds to NO protection. We could use + * set_range with range (start=0, len=0) but use of the aformentioned observation avoids + * any overheads. */ + status_sr1 = (status_sr1 & ~bitmask) & 0xff; + result = flash->chip->status_register->write(flash, SR1, (uint8_t)status_sr1); + if (result) { + msg_cerr("%s failed\n", __func__); + return result; + } + status_sr1 = flash->chip->status_register->read(flash, SR1); + if (status_sr1 & bitmask) { + msg_cdbg("Could not write configuration to status register to disable block protection\n"); + msg_cerr("%s failed\n", __func__); + return 1; + } + msg_cdbg("Disabled any block protection in effect\n"); + return result; +} diff --git a/writeprotect.h b/writeprotect.h new file mode 100644 index 0000000..41d9cf0 --- /dev/null +++ b/writeprotect.h @@ -0,0 +1,33 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2016 Hatim Kanchwala hatim@hatimak.me + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __WRITEPROTECT_H__ +#define __WRITEPROTECT_H__ 1 + +#include "flash.h" + +#define LEN_RANGES 64 + +struct range { + uint32_t start; + uint32_t len; /* in kB */ +}; + +#endif /* !__WRITEPROTECT_H__ */