Hi,
Following patches add new infrastructure for status register(s) and locking/unlocking of access protection. Detailed points for explaining each patch are included within them.
Some general thoughts - - The code has a lot TODOs and FIXMEs (most of which I have addressed to myself). They are short-term targets and represent the work-in-progress nature of the patches. Future revisions will resolve them. - There are a lot of comments. Eventually much of that will be relocated to documentation and wiki, so comments in future revisions will shrink. - Some CLI code is borrowed from the ChromiumOS fork (https://chromium.googlesource.com/chromiumos/third_party/flashrom/). IMO, the exact words to use for the options is subjective. - The names for struct status_register and struct wp defines (in statusreg_layouts.c and writeprotect_layouts.c respectively) are not very creative and tend to be long. I couldn't come up with a generic scheme that captures most of the information regarding it. You are welcome to offer creative suggestions ;)
Further work - - Read and write "configuration" registers (a lot of Spansion chips have those) - Add tested/untested status for new infrastructure similar to what we have for chips (like TEST_PREW, etc.) - write protection modes - protection ranges - Access protection for non-SPI chips (pointed to by Stefan over IRC) - Reuse layouts for locking/unlocking (pointed to by Stafan and David over IRC) - Handling WPS bit (a couple of GigaDevice chips have this) - Exotic chips (mostly Atmel, now Adesto, chips fall under this category)
This is what I particularly seek - - Feedback on code - Feedback on CLI - Any other ideas that you'd like to add tu further work - Please add support for a few chips that you might have, and if possible also test them - would like to know how cumbersome it is
Thank you for your time. I am looking forward to your feedback and having a discussion. I hope this contribution (and many more in the future) adds value to flashrom and its community.
Bye, Hatim
Hatim Kanchwala (6): Status register infrastructure Locking/unlocking access protection infrastructure WIP: Add support for status register infrastructure to existing chips WIP: Add support for access protection infrastructure to existing chips Integrate new infrastructure with existing codebase Add command-line interface to expose new infrastructure
Makefile | 6 +- chipdrivers.h | 24 +++ cli_classic.c | 200 +++++++++++++++++++++++- flash.h | 46 +++++- flashchips.c | 176 +++++++++++++++++---- flashrom.8.tmpl | 56 ++++++- flashrom.c | 17 +- spi.h | 8 +- spi25.c | 164 +++++++++++++++---- spi25_statusreg.c | 416 +++++++++++++++++++++++++++++++++++++++++++++++++ spi25_statusreg.h | 115 ++++++++++++++ statusreg_layouts.c | 320 +++++++++++++++++++++++++++++++++++++ writeprotect.c | 405 +++++++++++++++++++++++++++++++++++++++++++++++ writeprotect.h | 40 +++++ writeprotect_layouts.c | 166 ++++++++++++++++++++ 15 files changed, 2083 insertions(+), 76 deletions(-) create mode 100644 spi25_statusreg.h create mode 100644 statusreg_layouts.c create mode 100644 writeprotect.c create mode 100644 writeprotect.h create mode 100644 writeprotect_layouts.c
- Read and write multiple status registers - Read is straightforward and most chips share same opcodes for RDSR1, RDS2 and RDSR3 - For chips with 2 status registers, WRSR takes either 1 or 2 bytes. When only 1 byte is supplied (which was the preious behaviour), the 2nd status register is cleared. (Our code automatically takes care of this.) - For chips with 3 status registers, each register is separately written to and we have many chips sharing opcodes for WRSR1, WRSR2 and WRSR3 - Get, set or prettyprint write protection mode for status register(s). Functionality exposed through struct status_register member. - This is controlled by SRP/SRWD or SRP0/SRP1 bit(s). Chips with SRP0 and SRP1 will most likely have at least 2 status registers. - For chips with SRP/SRWD bit, we can get/set SOFTWARE (status register unlocked) or HARDWARE (status register locked/unlocked subject to WP#) protection modes. - For chips with SRP0 and SRP1, we can additionally get/set POWER_CYCLE (status registers locked until next power down-up cycle) or PERMANENT modes. - We can also automatically detect how WP# affects the HARDWARE write protection mode. - struct flashchip contains pointer to a struct status_register (for allowing flexibility of reuse), which in turn has members to represent layout of status register(s) and, pointers to functions to read, write, print, set_wp_mode, get_wp_mode, print_wp_mode. Function pointers allow flexibility to assign chip specific routines for exotic cases. - Prettyprinting of different status register bits is unified. Newer bits can be defined in enum status_register_bit and description written in statreg_bit_desc[][]. Functionality exposed through struct status_register member.
Signed-off-by: Hatim Kanchwala hatim@hatimak.me --- chipdrivers.h | 9 ++ flash.h | 23 ++- spi.h | 8 +- spi25_statusreg.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ spi25_statusreg.h | 79 +++++++++++ 5 files changed, 527 insertions(+), 2 deletions(-) create mode 100644 spi25_statusreg.h
diff --git a/chipdrivers.h b/chipdrivers.h index c85eac9..4c58f30 100644 --- a/chipdrivers.h +++ b/chipdrivers.h @@ -16,26 +16,27 @@ * along with this program; if not, write to the Free Software * 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 */
/* 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); @@ -53,27 +54,35 @@ int spi_block_erase_c7(struct flashctx *flash, unsigned int addr, unsigned int b int spi_block_erase_d7(struct flashctx *flash, unsigned int addr, unsigned int blocklen); int spi_block_erase_d8(struct flashctx *flash, unsigned int addr, unsigned int blocklen); int spi_block_erase_db(struct flashctx *flash, unsigned int addr, unsigned int blocklen); erasefunc_t *spi_get_erasefn_from_opcode(uint8_t opcode); int spi_chip_write_1(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int spi_byte_program(struct flashctx *flash, unsigned int addr, uint8_t databyte); int spi_nbyte_program(struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len); int spi_nbyte_read(struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len); int spi_read_chunked(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len, unsigned int chunksize); int spi_write_chunked(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len, unsigned int chunksize);
/* spi25_statusreg.c */ uint8_t spi_read_status_register(struct flashctx *flash); +uint8_t spi_read_status_register_generic(struct flashctx *flash, enum status_register_num SRn); int spi_write_status_register(struct flashctx *flash, int status); +int spi_write_status_register_generic(struct flashctx *flash, enum status_register_num SRn, uint8_t status); +enum status_register_num top_status_register(struct flashctx *flash); +char pos_bit(struct flashctx *flash, enum status_register_bit bit); +enum wp_mode get_wp_mode_generic(struct flashctx *flash); +int set_wp_mode_generic(struct flashctx *flash, enum wp_mode wp_mode); +int spi_prettyprint_status_register_generic(struct flashctx *flash, enum status_register_num SRn); +int spi_prettyprint_status_register_wp_generic(struct flashctx *flash); void spi_prettyprint_status_register_bit(uint8_t status, int bit); int spi_prettyprint_status_register_plain(struct flashctx *flash); int spi_prettyprint_status_register_default_welwip(struct flashctx *flash); int spi_prettyprint_status_register_bp1_srwd(struct flashctx *flash); int spi_prettyprint_status_register_bp2_srwd(struct flashctx *flash); int spi_prettyprint_status_register_bp3_srwd(struct flashctx *flash); int spi_prettyprint_status_register_bp4_srwd(struct flashctx *flash); int spi_prettyprint_status_register_bp2_bpl(struct flashctx *flash); int spi_prettyprint_status_register_bp2_tb_bpl(struct flashctx *flash); int spi_disable_blockprotect(struct flashctx *flash); int spi_disable_blockprotect_bp1_srwd(struct flashctx *flash); int spi_disable_blockprotect_bp2_srwd(struct flashctx *flash); int spi_disable_blockprotect_bp3_srwd(struct flashctx *flash); diff --git a/flash.h b/flash.h index da049d1..566b709 100644 --- a/flash.h +++ b/flash.h @@ -27,26 +27,28 @@ #include "platform.h"
#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" + #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. */ typedef uint32_t chipsize_t; /* Able to store the number of bytes of any supported flash memory. */ @@ -189,27 +191,46 @@ struct flashchip { * influence that behaviour. For testing just comment out the other * elements or set the function pointer to NULL. */ struct block_eraser { struct eraseblock { unsigned int size; /* Eraseblock size in bytes */ unsigned int count; /* Number of contiguous blocks with that size */ } eraseblocks[NUM_ERASEREGIONS]; /* a block_erase function should try to erase one block of size * 'blocklen' at address 'blockaddr' and return 0 on success. */ int (*block_erase) (struct flashctx *flash, unsigned int blockaddr, unsigned int blocklen); } block_erasers[NUM_ERASEFUNCTIONS];
- int (*printlock) (struct flashctx *flash); + /* The following struct represents the status register(s). Each status register + * is a member of the layout array at the corresponding index (starting at SR1=0). */ + struct status_register { + /* We need one more than MAX_STATUS_REGISTERS */ + enum status_register_bit layout[MAX_STATUS_REGISTERS + 1][8]; + + /* Return value of status register SRn. */ + 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 (*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 flashctx { struct flashchip *chip; /* FIXME: The memory mappings should be saved in a more structured way. */ diff --git a/spi.h b/spi.h index de5b3be..f061041 100644 --- a/spi.h +++ b/spi.h @@ -106,45 +106,51 @@ #define JEDEC_BE_D7_OUTSIZE 0x04 #define JEDEC_BE_D7_INSIZE 0x00
/* Sector Erase 0x20 is supported by Macronix/SST chips. */ #define JEDEC_SE 0x20 #define JEDEC_SE_OUTSIZE 0x04 #define JEDEC_SE_INSIZE 0x00
/* Page Erase 0xDB */ #define JEDEC_PE 0xDB #define JEDEC_PE_OUTSIZE 0x04 #define JEDEC_PE_INSIZE 0x00
-/* Read Status Register */ +/* Read Status Register(s) */ #define JEDEC_RDSR 0x05 #define JEDEC_RDSR_OUTSIZE 0x01 #define JEDEC_RDSR_INSIZE 0x01 +#define JEDEC_RDSR2 0x35 +#define JEDEC_RDSR3 0x15
/* Status Register Bits */ #define SPI_SR_WIP (0x01 << 0) #define SPI_SR_WEL (0x01 << 1) #define SPI_SR_AAI (0x01 << 6)
/* Write Status Enable */ #define JEDEC_EWSR 0x50 #define JEDEC_EWSR_OUTSIZE 0x01 #define JEDEC_EWSR_INSIZE 0x00
/* Write Status Register */ #define JEDEC_WRSR 0x01 #define JEDEC_WRSR_OUTSIZE 0x02 #define JEDEC_WRSR_INSIZE 0x00 +#define JEDEC_WRSR_2_OUTSIZE 0x03 +#define JEDEC_WRSR1 0x01 +#define JEDEC_WRSR2 0x31 +#define JEDEC_WRSR3 0x11
/* Read the memory */ #define JEDEC_READ 0x03 #define JEDEC_READ_OUTSIZE 0x04 /* JEDEC_READ_INSIZE : any length */
/* Write memory byte */ #define JEDEC_BYTE_PROGRAM 0x02 #define JEDEC_BYTE_PROGRAM_OUTSIZE 0x05 #define JEDEC_BYTE_PROGRAM_INSIZE 0x00
/* Write AAI word (SST25VF080B) */ #define JEDEC_AAI_WORD_PROGRAM 0xad diff --git a/spi25_statusreg.c b/spi25_statusreg.c index 01a6862..ac77c1d 100644 --- a/spi25_statusreg.c +++ b/spi25_statusreg.c @@ -1,39 +1,82 @@ /* * This file is part of the flashrom project. * It handles everything related to status registers of the JEDEC family 25. * * Copyright (C) 2007, 2008, 2009, 2010 Carl-Daniel Hailfinger * Copyright (C) 2008 coresystems GmbH * Copyright (C) 2008 Ronald Hoogenboom ronald@zonnet.nl * Copyright (C) 2012 Stefan Tauner + * 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; version 2 of the License. * * 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 "flash.h" #include "chipdrivers.h" #include "spi.h" +#include "spi25_statusreg.h" + +/* The following multi-array contains descriptions of corresponding bits defined + * in enum status_register_bit. It is imperative that the correspondence between + * the two definitions remain intact. The first index defines the correspondence. */ +char *statreg_bit_desc[][2] = { + /* Name, Description */ + { "", "" }, /* Corresponds to INVALID_BIT. */ + { "RESV", "" }, /* Corresponds to RESV. */ + { "WIP", "BUSY/Write In Progress (WIP)" }, + { "WEL", "Write Enable Latch (WEL)" }, + { "SRP0", "Status Register Write Disable (SRWD)/Software Register Protect (SRP/SRP0)" }, + { "SRP1", "Software Register Protect 1 (SRP1)" }, + { "BPL", "Block Protect Write Disable (BPL)" }, + { "WP", "WP# Disable (WPDIS)" }, + { "CMP", "Complement Protect (CMP)" }, + { "WPS", "Write Protect Scheme (WPS)" }, + { "QE", "Quad Enable (QE)" }, + { "SUS", "Erase/Program Suspend (SUS)" }, + { "SUS1", "Erase Suspend (SUS1)" }, + { "SUS2", "Program Suspend (SUS2)" }, + { "DRV0", "Output Driver Strength (DRV0)" }, + { "DRV1", "Output Driver Strength (DRV1)" }, + { "RST", "HOLD/Reset (RST)" }, + { "HPF", "HPF/HPM (High Performance Flag)" }, + { "LPE", "Low Power Enable (LPE)" }, + { "AAI", "Auto Address Increment (AAI)" }, + { "APT", "All Protect (APT)" }, + { "CP", "Continuously Program mode (CP)" }, + /* The order of the following bits must not be altered and + * newer entries must not be inserted between them. */ + { "BP0", "Block Protect 0 (BP0)" }, + { "BP1", "Block Protect 1 (BP1)" }, + { "BP2", "Block Protect 2 (BP2)" }, + { "BP3", "Block Protect 3 (BP3)" }, + { "BP4", "Block Protect 4 (BP4)" }, + { "TB", "Top/Bottom Block Protect (TB)" }, + { "SEC", "Sector/Block Protect (SEC)" }, + { "LB1", "Security Register Lock (LB/LB1)" }, + { "LB2", "Security Register Lock (LB2)" }, + { "LB3", "Security Register Lock (LB3)" }, +};
/* === Generic functions === */ int spi_write_status_enable(struct flashctx *flash) { static const unsigned char cmd[JEDEC_EWSR_OUTSIZE] = { JEDEC_EWSR }; int result;
/* Send EWSR (Enable Write Status Register). */ result = spi_send_command(flash, sizeof(cmd), JEDEC_EWSR_INSIZE, cmd, NULL);
if (result) msg_cerr("%s failed\n", __func__);
@@ -98,41 +141,336 @@ int spi_write_status_register(struct flashctx *flash, int status)
if (!(feature_bits & (FEATURE_WRSR_WREN | FEATURE_WRSR_EWSR))) { msg_cdbg("Missing status register write definition, assuming " "EWSR is needed\n"); feature_bits |= FEATURE_WRSR_EWSR; } if (feature_bits & FEATURE_WRSR_WREN) ret = spi_write_status_register_flag(flash, status, JEDEC_WREN); if (ret && (feature_bits & FEATURE_WRSR_EWSR)) ret = spi_write_status_register_flag(flash, status, JEDEC_EWSR); return ret; }
+// TODO(hatim): This function should be decommissioned once integration is complete uint8_t spi_read_status_register(struct flashctx *flash) { static const unsigned char cmd[JEDEC_RDSR_OUTSIZE] = { JEDEC_RDSR }; /* FIXME: No workarounds for driver/hardware bugs in generic code. */ unsigned char readarr[2]; /* JEDEC_RDSR_INSIZE=1 but wbsio needs 2 */ int ret;
/* Read Status Register */ ret = spi_send_command(flash, sizeof(cmd), sizeof(readarr), cmd, readarr); if (ret) msg_cerr("RDSR failed!\n");
return readarr[0]; }
+/* Generic function to read status register SRn (n = 1, 2, or 3). */ +// TODO(hatim): This should eventually replace calls to spi_read_status_register(). +uint8_t spi_read_status_register_generic(struct flashctx *flash, enum status_register_num SRn) +{ + // TODO(hatim): Check whether flash has SRn in the first place + static unsigned char const cmd[MAX_STATUS_REGISTERS][JEDEC_RDSR_OUTSIZE] = { + { JEDEC_RDSR }, { JEDEC_RDSR2 }, { JEDEC_RDSR3 } + }; + unsigned char readarr[JEDEC_RDSR_INSIZE]; + + if (spi_send_command(flash, sizeof(cmd[SRn]), sizeof(readarr), cmd[SRn], readarr)) + msg_cerr("%s for SR%d failed.\n", __func__, SRn + 1); + return (uint8_t)readarr[0]; +} + +/* Called by spi_write_status_register_generic() to set status for chips with + * exactly 2 status registers. */ +static int spi_write_status_generic_2(struct flashctx *flash, uint16_t status) +{ + int result = spi_write_enable(flash); + if (result) { + msg_cerr("%s failed\n", __func__); + return result; + } + + unsigned char cmd[JEDEC_WRSR_2_OUTSIZE] = { JEDEC_WRSR, status & 0xff, (status >> 8) & 0xff }; + result = spi_send_command(flash, sizeof(cmd), 0, cmd, NULL); + if (result) + msg_cerr("%s failed\n", __func__); + // FIXME(hatim): Verify whether status was indeed written (?) + return result; +} + +/* Called by spi_write_status_register_generic() to set status for chips with + * exactly 3 status registers. */ +static int spi_write_status_generic_3(struct flashctx *flash, enum status_register_num SRn, uint8_t status) +{ + int result = spi_write_enable(flash); + if (result) { + msg_cerr("%s failed\n", __func__); + return result; + } + + unsigned char cmd[JEDEC_WRSR_OUTSIZE]; + switch (SRn) { + case SR1: + cmd[0] = JEDEC_WRSR1; + break; + case SR2: + cmd[0] = JEDEC_WRSR2; + break; + case SR3: + cmd[0] = JEDEC_WRSR3; + break; + } + cmd[1] = status; + result = spi_send_command(flash, sizeof(cmd), 0, cmd, NULL); + if (result) + msg_cerr("%s failed\n", __func__); + // FIXME(hatim): Verify whether status was indeed written (?) + return result; +} + +/* Generic function to set status register SRn (n = 1, 2, or 3) to status. */ +// TODO(hatim): This should eventually replace calls to spi_write_status_register() +// and, consequently spi_write_status_register() should be made static. +int spi_write_status_register_generic(struct flashctx *flash, enum status_register_num SRn, uint8_t status) +{ + // TODO(hatim): Check whether flash has SRn in the first place + int result = 0; + uint8_t other_status; + uint16_t total_status; + + switch (top_status_register(flash)) { + case SR1: + result = spi_write_status_register(flash, (int)status); + break; + case SR2: + /* Use specific read function if available */ + if (flash->chip->status_register->read) + other_status = flash->chip->status_register->read(flash, SRn); + else + other_status = spi_read_status_register_generic(flash, (SRn == SR1) ? SR2 : SR1); + total_status = (SRn == SR1) ? ((other_status << 8) | status) : ((status << 8) | other_status); + result = spi_write_status_generic_2(flash, total_status); + break; + case SR3: + result = spi_write_status_generic_3(flash, SRn, status); + break; + } + if (result) + msg_cerr("%s failed\n", __func__); + return result; +} + +/* Return top-most (highest) status register. */ +enum status_register_num top_status_register(struct flashctx *flash) { + enum status_register_num SRn = SR1; + enum status_register_bit (*layout)[8] = flash->chip->status_register->layout; + + while (layout[SRn++][0] != INVALID_BIT) + ; + return SRn - 2; +} + +/* Given a bit, return position of the bit in status register or -1 if not found. + * Counting starts from 0 for bit_0 of SR1, moves to 8 for bit_0 of SR2 and, + * finally moves to 23 for bit_7 of SR3. */ +char pos_bit(struct flashctx *flash, enum status_register_bit bit) { + char pos = -1; + + for (enum status_register_num SRn = SR1; SRn <= top_status_register(flash); SRn++) { + for (int j = 0; j < 8; j++) { + if (flash->chip->status_register->layout[SRn][j] == bit) { + pos = (8 * SRn) + j; + break; + } + } + } + return pos; +} + +// TODO(hatim): Improve code, merge different switch-cases into one +enum wp_mode get_wp_mode_generic(struct flashctx *flash) +{ + // TODO(hatim): Check whether chip has said status register and bits in the first place + int result, status, status_tmp, srp_mask; + + if (pos_bit(flash, SRP1) != -1) { + /* The following code assumes that SRP0 and SRP1 are present in the first and second + * status registers as bit_7 and bit_8 respectively. */ + status = flash->chip->status_register->read(flash, SR1); + status |= flash->chip->status_register->read(flash, SR2) << 8; + srp_mask = 1 << 7 | 1 << 8; + + switch ((status & srp_mask) >> 7) { + case 0x00: + return WP_MODE_SOFTWARE; + case 0x01: + /* Make (SRP1, SRP0) = (0, 0). */ + status_tmp = (status & ~srp_mask) & 0xffff; + /* Only need to write SRP0=0, which is always present in the first status register. */ + if (flash->chip->status_register->write(flash, SR1, (uint8_t)(status_tmp & 0xff))) { + /* FIXME: If flash->chip->status_register->write() verifies whether status + * supplied to it is indeed written, then, if we are in this block, we should + * simply return WP_MODE_HARDWARE_PROTECTED because failure is a sufficient + * indication for hardware protection mode to be on. */ + msg_cerr("%s failed\n", __func__); + return WP_MODE_INVALID; + } + status_tmp = flash->chip->status_register->read(flash, SR1); + status_tmp |= flash->chip->status_register->read(flash, SR2) << 8; + result = (status_tmp & srp_mask) >> 7; + if (result == 0x01) { + return WP_MODE_HARDWARE_PROTECTED; + } else if (result == 0x00) { + status_tmp = ((status_tmp & ~srp_mask) | (0x01 << 7)) & 0xffff; + if (flash->chip->status_register->write(flash, SR1, + (uint8_t)(status_tmp & 0xff))) { + msg_cerr("%s failed\n", __func__); + return WP_MODE_SOFTWARE; + } + return WP_MODE_HARDWARE_UNPROTECTED; + } else { + return WP_MODE_INVALID; + } + case 0x02: + return WP_MODE_POWER_CYCLE; + case 0x03: + return WP_MODE_PERMANENT; + default: + return WP_MODE_INVALID; + } + } else { + status = flash->chip->status_register->read(flash, SR1); + srp_mask = 1 << 7; + + switch ((status & srp_mask) >> 7) { + case 0x00: + return WP_MODE_SOFTWARE; + case 0x01: + /* Make SRP0 = 0. */ + status_tmp = (status & ~srp_mask) & 0xff; + result = flash->chip->status_register->write(flash, SR1, (uint8_t)status_tmp); + if (result) { + /* FIXME: If flash->chip->status_register->write() verifies whether status + * supplied to it is indeed written, then, if we are in this block, we should + * simply return WP_MODE_HARDWARE_PROTECTED because failure is a sufficient + * indication for hardware protection mode to be on. */ + msg_cerr("%s failed\n", __func__); + return WP_MODE_INVALID; + } + status_tmp = flash->chip->status_register->read(flash, SR1); + result = (status_tmp & srp_mask) >> 7; + if (result == 0x01) { + return WP_MODE_HARDWARE_PROTECTED; + } else if (result == 0x00) { + status_tmp = ((status_tmp & ~srp_mask) | (0x01 << 7)) & 0xff; + if (flash->chip->status_register->write(flash, SR1, (uint8_t)status_tmp)) { + msg_cerr("%s failed\n", __func__); + return WP_MODE_SOFTWARE; + } + return WP_MODE_HARDWARE_UNPROTECTED; + } else { + return WP_MODE_INVALID; + } + default: + return WP_MODE_INVALID; + } + } +} + +// TODO(hatim): Improve code, merge different switch-cases into one +int set_wp_mode_generic(struct flashctx *flash, enum wp_mode wp_mode) +{ + int srp_mask = 1 << 7, srp_bitfield, srp_bitfield_new, result; + int status = flash->chip->status_register->read(flash, SR1); + + if (pos_bit(flash, SRP1) != -1) { + /* The following code assumes that SRP0 and SRP1 are present and that they + * are present in the first and second status registers as bit_7 and bit_8 + * respectively. */ + status |= flash->chip->status_register->read(flash, SR2) << 8; + /* SRP1 is bit_8. */ + srp_mask |= 1 << 8; + switch (wp_mode) { + case WP_MODE_SOFTWARE: + srp_bitfield = 0x00; + break; + case WP_MODE_HARDWARE_UNPROTECTED: + srp_bitfield = 0x01; + break; + case WP_MODE_POWER_CYCLE: + srp_bitfield = 0x02; + break; + case WP_MODE_PERMANENT: + srp_bitfield = 0x03; + break; + default: + msg_cdbg("Invalid write protection mode for status register(s)\n"); + msg_cerr("%s failed\n", __func__); + return -1; + } + status = ((status & ~srp_mask) | (srp_bitfield << 7)) & 0xffff; + result = flash->chip->status_register->write(flash, SR1, (uint8_t)(status & 0xff)); + if (result) { + msg_cerr("%s failed", __func__); + return result; + } + result = flash->chip->status_register->write(flash, SR2, (uint8_t)((status >> 8) & 0xff)); + if (result) { + msg_cerr("%s failed", __func__); + return result; + } + status = flash->chip->status_register->read(flash, SR1); + status |= flash->chip->status_register->read(flash, SR2) << 8; + srp_bitfield_new = (status & srp_mask ) >> 7; + if (srp_bitfield_new != srp_bitfield) { + msg_cdbg("Setting write protection mode for status register(s) failed\n"); + msg_cerr("%s failed\n", __func__); + return -1; + } + } else { + switch (wp_mode) { + case WP_MODE_SOFTWARE: + srp_bitfield = 0x00; + break; + case WP_MODE_HARDWARE_UNPROTECTED: + srp_bitfield = 0x01; + break; + case WP_MODE_POWER_CYCLE: + case WP_MODE_PERMANENT: + default: + msg_cdbg("Invalid write protection mode for status register(s)\n"); + msg_cerr("%s failed\n", __func__); + return -1; + } + status = ((status & ~srp_mask) | (srp_bitfield << 7)) & 0xff; + result = flash->chip->status_register->write(flash, SR1, (uint8_t)status); + if (result) { + msg_cerr("%s failed", __func__); + return result; + } + status = flash->chip->status_register->read(flash, SR1); + srp_bitfield_new = (status & srp_mask ) >> 7; + if (srp_bitfield_new != srp_bitfield) { + msg_cdbg("Setting write protection mode for status register(s) failed\n"); + msg_cerr("%s failed\n", __func__); + return -1; + } + } + return 0; +} + /* A generic block protection disable. * Tests if a protection is enabled with the block protection mask (bp_mask) and returns success otherwise. * Tests if the register bits are locked with the lock_mask (lock_mask). * Tests if a hardware protection is active (i.e. low pin/high bit value) with the write protection mask * (wp_mask) and bails out in that case. * If there are register lock bits set we try to disable them by unsetting those bits of the previous register * contents that are set in the lock_mask. We then check if removing the lock bits has worked and continue as if * 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. * @@ -215,188 +553,260 @@ int spi_disable_blockprotect_bp2_srwd(struct flashctx *flash) * protected/locked by bit #7. */ 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. */ 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 static void spi_prettyprint_status_register_srwd(uint8_t status) { msg_cdbg("Chip status register: Status Register Write Disable (SRWD, SRP, ...) is %sset\n", (status & (1 << 7)) ? "" : "not "); }
/* Common highest bit: Block Protect Write Disable (BPL). */ +// TODO(hatim): This function should be decommissioned once integration is complete static void spi_prettyprint_status_register_bpl(uint8_t status) { msg_cdbg("Chip status register: Block Protect Write Disable (BPL) is %sset\n", (status & (1 << 7)) ? "" : "not "); }
/* Common lowest 2 bits: WEL and WIP. */ +// TODO(hatim): This function should be decommissioned once integration is complete static void spi_prettyprint_status_register_welwip(uint8_t status) { msg_cdbg("Chip status register: Write Enable Latch (WEL) is %sset\n", (status & (1 << 1)) ? "" : "not "); msg_cdbg("Chip status register: Write In Progress (WIP/BUSY) is %sset\n", (status & (1 << 0)) ? "" : "not "); }
/* Common block protection (BP) bits. */ +// TODO(hatim): This function should be decommissioned once integration is complete static void spi_prettyprint_status_register_bp(uint8_t status, int bp) { switch (bp) { /* Fall through. */ case 4: msg_cdbg("Chip status register: Block Protect 4 (BP4) is %sset\n", (status & (1 << 6)) ? "" : "not "); case 3: msg_cdbg("Chip status register: Block Protect 3 (BP3) is %sset\n", (status & (1 << 5)) ? "" : "not "); case 2: msg_cdbg("Chip status register: Block Protect 2 (BP2) is %sset\n", (status & (1 << 4)) ? "" : "not "); case 1: msg_cdbg("Chip status register: Block Protect 1 (BP1) is %sset\n", (status & (1 << 3)) ? "" : "not "); case 0: msg_cdbg("Chip status register: Block Protect 0 (BP0) is %sset\n", (status & (1 << 2)) ? "" : "not "); } }
/* Unnamed bits. */ +// TODO(hatim): This function should be decommissioned once integration is complete void spi_prettyprint_status_register_bit(uint8_t status, int bit) { msg_cdbg("Chip status register: Bit %i is %sset\n", bit, (status & (1 << bit)) ? "" : "not "); }
+// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_plain(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status); return 0; }
/* Print the plain hex value and the welwip bits only. */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_default_welwip(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status);
spi_prettyprint_status_register_welwip(status); return 0; }
/* Works for many chips of the * AMIC A25L series * and MX MX25L512 */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_bp1_srwd(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status);
spi_prettyprint_status_register_srwd(status); spi_prettyprint_status_register_bit(status, 6); spi_prettyprint_status_register_bit(status, 5); spi_prettyprint_status_register_bit(status, 4); spi_prettyprint_status_register_bp(status, 1); spi_prettyprint_status_register_welwip(status); return 0; }
/* Works for many chips of the * AMIC A25L series * PMC Pm25LD series */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_bp2_srwd(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status);
spi_prettyprint_status_register_srwd(status); spi_prettyprint_status_register_bit(status, 6); spi_prettyprint_status_register_bit(status, 5); spi_prettyprint_status_register_bp(status, 2); spi_prettyprint_status_register_welwip(status); return 0; }
/* Works for many chips of the * ST M25P series * MX MX25L series */ +// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_bp3_srwd(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status);
spi_prettyprint_status_register_srwd(status); spi_prettyprint_status_register_bit(status, 6); spi_prettyprint_status_register_bp(status, 3); spi_prettyprint_status_register_welwip(status); return 0; }
+// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_bp4_srwd(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status);
spi_prettyprint_status_register_srwd(status); spi_prettyprint_status_register_bp(status, 4); spi_prettyprint_status_register_welwip(status); return 0; }
+// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_bp2_bpl(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status);
spi_prettyprint_status_register_bpl(status); spi_prettyprint_status_register_bit(status, 6); spi_prettyprint_status_register_bit(status, 5); spi_prettyprint_status_register_bp(status, 2); spi_prettyprint_status_register_welwip(status); return 0; }
+// TODO(hatim): This function should be decommissioned once integration is complete int spi_prettyprint_status_register_bp2_tb_bpl(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status);
spi_prettyprint_status_register_bpl(status); spi_prettyprint_status_register_bit(status, 6); msg_cdbg("Chip status register: Top/Bottom (TB) is %s\n", (status & (1 << 5)) ? "bottom" : "top"); spi_prettyprint_status_register_bp(status, 2); spi_prettyprint_status_register_welwip(status); return 0; }
+/* TODO: Use in place of printlock, after asigning a struct status_register member. This supersedes + * functionality of all prettyprint functions defined above. */ +int spi_prettyprint_status_register_generic(struct flashctx *flash, enum status_register_num SRn) +{ + uint8_t status = flash->chip->status_register->read(flash, SRn); + enum status_register_bit bit; + + msg_cdbg("Chip status register %d is 0x%02x.\n", SRn + 1, status); + for (int i = 7; i >= 0; i--) { + switch (bit = flash->chip->status_register->layout[SRn][i]) { + case RESV: + msg_cdbg("Chip status register %d: Bit %d is reserved\n", SRn + 1, i); + break; + case TB: + msg_cdbg("Chip status register %d : %s is %s\n", SRn + 1, statreg_bit_desc[bit][1], + (status & (1 << i)) ? "bottom" : "top"); + break; + case SEC: + msg_cdbg("Chip status register %d: %s is %s\n", SRn + 1, statreg_bit_desc[bit][1], + (status & (1 << i)) ? "sectors" : "blocks"); + default: + msg_cdbg("Chip status register %d: %s is %sset\n", SRn + 1, statreg_bit_desc[bit][1], + (status & (1 << i)) ? "" : "not "); + break; + } + } + return 0; +} + +int spi_prettyprint_status_register_wp_generic(struct flashctx *flash) +{ + uint8_t multiple = (top_status_register(flash) == SR1) ? 0 : 1; + switch (flash->chip->status_register->get_wp_mode(flash)) { + case WP_MODE_INVALID: + msg_cdbg("Invalid write protection mode for status register%s\n", (multiple) ? "s" : ""); + break; + case WP_MODE_POWER_CYCLE: + msg_cdbg("Power supply lock down, cannot write to status register%s until next power " + "down-up cycle\n", (multiple) ? "s" : ""); + break; + case WP_MODE_PERMANENT: + msg_cdbg("Status register%s permanently locked\n", (multiple) ? "s are" : " is"); + break; + case WP_MODE_HARDWARE_PROTECTED: + msg_cerr("Hardware protection of status register%s is active (WP# pin low)," + " disabling impossible\n", (multiple) ? "s" : ""); + break; + case WP_MODE_HARDWARE_UNPROTECTED: + msg_cdbg("Hardware protection is active (WP# pin high), writes to status " + "register%s still possible\n", (multiple) ? "s are" : " is"); + break; + case WP_MODE_SOFTWARE: + msg_cdbg("Write protection for status register%s is NOT in effect\n", (multiple) ? "s" : ""); + break; + } + return 0; +} + /* === Amic === * FIXME: spi_disable_blockprotect is incorrect but works fine for chips using * spi_prettyprint_status_register_bp1_srwd or * spi_prettyprint_status_register_bp2_srwd. * FIXME: spi_disable_blockprotect is incorrect and will fail for chips using * spi_prettyprint_status_register_amic_a25l032 if those have locks controlled * by the second status register. */
int spi_prettyprint_status_register_amic_a25l032(struct flashctx *flash) { uint8_t status = spi_read_status_register(flash); spi_prettyprint_status_register_hex(status); diff --git a/spi25_statusreg.h b/spi25_statusreg.h new file mode 100644 index 0000000..b082f45 --- /dev/null +++ b/spi25_statusreg.h @@ -0,0 +1,79 @@ +/* + * 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 __SPI25_STATUSREG_H__ +#define __SPI25_STATUSREG_H__ 1 + +#include "flash.h" + +/* It has been observed that chips have at most 3 status registers. Please + * update the define if a chip with higher status registers is found. */ +#define MAX_STATUS_REGISTERS 3 + +enum bit_state { + DONT_CARE = -1, /* Don't care */ + OFF = 0, + ON = 1, +}; + +/* The following enum defines bits found in status registers. Datasheets + * will mostly name bits in the same manner. */ +// TODO(hatim): Add other remaining bits +enum status_register_bit { + INVALID_BIT = 0, /* This must always stay at the top. */ + RESV, WIP, WEL, /* WIP is also referred as BUSY. */ + /* SRP0 is same as SRP and SRWD. */ + SRP0, SRP1, BPL, WP, CMP, WPS, QE, SUS, SUS1, SUS2, DRV0, DRV1, RST, HPF, LPE, AAI, + APT, CP, + /* The order of the following bits must not be altered and newer entries must not + * be inserted in between them. */ + BP0, BP1, BP2, BP3, BP4, TB, SEC, LB1, LB2, LB3 /* LB1 is same as LB. */ +}; + +enum status_register_num { SR1 = 0, SR2, SR3 }; + +/* The following enum defines write protection modes available for status registers. */ +enum wp_mode { + WP_MODE_INVALID = 0, + + /* Status register is unlocked and can be written to after a WREN. */ + WP_MODE_SOFTWARE, + + /* When WP# is low, status register is locked and can not be written to. + * In this mode SRP0=1, and SRP1=1 if present. */ + WP_MODE_HARDWARE_PROTECTED, + + /* When WP# is high status register is unlocked and can be written + * to after WREN. In this mode SRP0=1. */ + WP_MODE_HARDWARE_UNPROTECTED, + + /* Status register is protected and can not be written to until next + * power down/up cycle, post which status register will be unlocked + * and can be written to after a WREN. */ + WP_MODE_POWER_CYCLE, + + /* Status register is permanently protected and cannot be written to. */ + WP_MODE_PERMANENT, +}; + +/* Describes corresponding bits from enum status_register_bit */ +extern char *statreg_bit_desc[][2]; + +#endif /* !__SPI25_STATUSREG_H__ */
- 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__ */
- struct status_register defines added for the following chips (66 in total) - - AMIC(4) : A25L080, A25LQ16, A25LQ32A, A25L032 - Macronix(15) : MX25L6408E, MX25L6406E, MX25L1605D, MX25L3205D, MX25L6405D, MX25L1608D, MX25L3208D, MX25L6408D, MX25L6436E, MX25L6445E, MX25L6465E, MX25L12865E, MX25L12845E, MX25L12835F, MX25L1673E - GigaDevice(41) : GD25LQ16, GD25LQ40, GD25LQ80B, GD25LQ40B, GD25LQ64C, GD25LQ80, GD25LQ128C, GD25LQ32C, GD25LQ16, GD25LQ40, GD25LQ80B, GD25LQ40B, GD25LQ64C, GD25LQ80, GD25LQ128C, GD25LQ32C, GD25Q16B, GD25Q32B, GD25Q64B, GD25Q10, GD25Q16, GD25Q20, GD25Q40, GD25Q80, GD25VQ16C, GD25VQ80C, GD25Q16C, GD25Q40C, GD25VQ21B, GD25VQ41B, GD25Q21B, GD25Q41B, GD25Q80B, GD25Q128, GD25LQ05B, GD25LQ10B, GD25LQ20B, GD25Q32C, GD25Q64C, GD25Q127C, GD25Q128C - Winbond(6) : W25Q80, W25Q16, W25Q32, W25Q40BL, W25Q64FV, W25Q128FW - 16 of the above support new infrastructure (in flashchips.c) (WIP) - 19 unique struct definitions were required to represent all of the above chips. - Note that quite a few chips don't have support in flashrom (yet).
Signed-off-by: Hatim Kanchwala hatim@hatimak.me --- Makefile | 2 +- flashchips.c | 169 +++++++++++++++++++++++---- spi25_statusreg.h | 36 ++++++ statusreg_layouts.c | 320 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 505 insertions(+), 22 deletions(-) create mode 100644 statusreg_layouts.c
diff --git a/Makefile b/Makefile index de08492..ed4979e 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 \ - writeprotect.o opaque.o sfdp.o en29lv640b.o at45db.o + writeprotect.o statusreg_layouts.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/flashchips.c b/flashchips.c index 40b6b8e..0afd297 100644 --- a/flashchips.c +++ b/flashchips.c @@ -15,26 +15,27 @@ * 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 "flash.h" #include "flashchips.h" #include "chipdrivers.h" +#include "spi25_statusreg.h"
/** * List of supported flash chips. * * Please keep the list sorted by vendor name and chip family, so that the output of 'flashrom -L' is roughly * alphabetically sorted. Within families keep them in order of density. */ const struct flashchip flashchips[] = {
/* * .vendor = Vendor name * .name = Chip name * .bustype = Supported flash bus types (Parallel, LPC...) @@ -1155,27 +1156,28 @@ const struct flashchip flashchips[] = { .block_erasers = { { .eraseblocks = { { 4 * 1024, 256 } }, .block_erase = spi_block_erase_20, }, { .eraseblocks = { { 64 * 1024, 16 } }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { { 1024 * 1024, 1 } }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp2_srwd, + /* TODO: Read/write 2nd status register */ + .status_register = &a25l080_sr, .unlock = spi_disable_blockprotect, .write = spi_chip_write_256, .read = spi_chip_read, .voltage = {2700, 3600}, },
{ .vendor = "AMIC", .name = "A25L016", .bustype = BUS_SPI, .manufacture_id = AMIC_ID_NOPREFIX, .model_id = AMIC_A25L016, .total_size = 2048, @@ -1265,36 +1267,37 @@ const struct flashchip flashchips[] = { .eraseblocks = { { 64 * 1024, 32 } }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { { 64 * 1024, 32 } }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { { 2048 * 1024, 1 } }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { { 2048 * 1024, 1 } }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_amic_a25l032, /* bit5: T/B, bit6: prot size */ - .unlock = spi_disable_blockprotect_bp2_srwd, /* TODO: 2nd status reg (read with 0x35) */ + /* TODO: Read/write 2nd status register */ + .status_register = &a25lq16_32a_sr, + .unlock = spi_disable_blockprotect_bp2_srwd, .write = spi_chip_write_256, .read = spi_chip_read, .voltage = {2700, 3600}, },
{ .vendor = "AMIC", - .name = "A25LQ032/A25LQ32A", + .name = "A25LQ032", .bustype = BUS_SPI, .manufacture_id = AMIC_ID_NOPREFIX, .model_id = AMIC_A25LQ032, .total_size = 4096, .page_size = 256, /* A25LQ32A supports SFDP */ /* OTP: 64B total; read 0x4B, 0x48; write 0x42 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_UNTESTED, .probe = probe_spi_rdid, .probe_timing = TIMING_ZERO, .block_erasers = { @@ -1305,27 +1308,68 @@ const struct flashchip flashchips[] = { .eraseblocks = { { 64 * 1024, 64 } }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { { 64 * 1024, 64 } }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { { 4096 * 1024, 1 } }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { { 4096 * 1024, 1 } }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_amic_a25l032, /* bit5: T/B, bit6: prot size */ + /* TODO: Read/write 2nd status register */ + .status_register = &a25l032_sr, + .unlock = spi_disable_blockprotect_bp2_srwd, + .write = spi_chip_write_256, + .read = spi_chip_read, + .voltage = {2700, 3600}, + }, + + { + .vendor = "AMIC", + .name = "A25LQ32A", + .bustype = BUS_SPI, + .manufacture_id = AMIC_ID_NOPREFIX, + .model_id = AMIC_A25LQ032, + .total_size = 4096, + .page_size = 256, + /* A25LQ32A supports SFDP */ + /* OTP: 64B total; read 0x4B, 0x48; write 0x42 */ + .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, + .tested = TEST_UNTESTED, + .probe = probe_spi_rdid, + .probe_timing = TIMING_ZERO, + .block_erasers = + { + { + .eraseblocks = { { 4 * 1024, 1024 } }, + .block_erase = spi_block_erase_20, + }, { + .eraseblocks = { { 64 * 1024, 64 } }, + .block_erase = spi_block_erase_52, + }, { + .eraseblocks = { { 64 * 1024, 64 } }, + .block_erase = spi_block_erase_d8, + }, { + .eraseblocks = { { 4096 * 1024, 1 } }, + .block_erase = spi_block_erase_60, + }, { + .eraseblocks = { { 4096 * 1024, 1 } }, + .block_erase = spi_block_erase_c7, + } + }, + .status_register = &a25lq16_32a_sr, .unlock = spi_disable_blockprotect_bp2_srwd, /* TODO: 2nd status reg (read with 0x35) */ .write = spi_chip_write_256, .read = spi_chip_read, .voltage = {2700, 3600}, },
{ .vendor = "AMIC", .name = "A25LQ64", .bustype = BUS_SPI, .manufacture_id = AMIC_ID_NOPREFIX, .model_id = AMIC_A25LQ64, .total_size = 8192, @@ -5660,28 +5704,29 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 16} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 8} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {512 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {512 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp4_srwd, - .unlock = spi_disable_blockprotect_bp4_srwd, /* TODO: 2nd status reg (read with 0x35) */ + /* TODO: Read/write 2nd status register */ + .status_register = &gd25lq_sr, + .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {1695, 1950}, },
{ .vendor = "GigaDevice", .name = "GD25LQ80", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25LQ80, .total_size = 1024, .page_size = 256, @@ -5699,28 +5744,29 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 32} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 16} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {1 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {1 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp4_srwd, - .unlock = spi_disable_blockprotect_bp4_srwd, /* TODO: 2nd status reg (read with 0x35) */ + /* TODO: Read/write 2nd status register*/ + .status_register = &gd25lq_sr, + .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {1695, 1950}, },
{ .vendor = "GigaDevice", .name = "GD25LQ16", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25LQ16, .total_size = 2048, .page_size = 256, @@ -5738,28 +5784,29 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 64} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp4_srwd, - .unlock = spi_disable_blockprotect_bp4_srwd, /* TODO: 2nd status reg (read with 0x35) */ + /* TODO: Read/write 2nd status register */ + .status_register = &gd25lq_sr, + .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {1695, 1950}, },
{ .vendor = "GigaDevice", .name = "GD25LQ32", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25LQ32, .total_size = 4096, .page_size = 256, @@ -6050,27 +6097,66 @@ const struct flashchip flashchips[] = { .eraseblocks = { {1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, .printlock = spi_prettyprint_status_register_bp4_srwd, .unlock = spi_disable_blockprotect_bp4_srwd, /* TODO: 2nd status reg (read with 0x35) */ .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {2700, 3600}, },
{ .vendor = "GigaDevice", - .name = "GD25Q16(B)", + .name = "GD25Q16", + .bustype = BUS_SPI, + .manufacture_id = GIGADEVICE_ID, + .model_id = GIGADEVICE_GD25Q16, + .total_size = 2048, + .page_size = 256, + .feature_bits = FEATURE_WRSR_WREN, + .tested = TEST_OK_PREW, + .probe = probe_spi_rdid, + .probe_timing = TIMING_ZERO, + .block_erasers = + { + { + .eraseblocks = { {4 * 1024, 512} }, + .block_erase = spi_block_erase_20, + }, { + .eraseblocks = { {32 * 1024, 64} }, + .block_erase = spi_block_erase_52, + }, { + .eraseblocks = { {64 * 1024, 32} }, + .block_erase = spi_block_erase_d8, + }, { + .eraseblocks = { {2 * 1024 * 1024, 1} }, + .block_erase = spi_block_erase_60, + }, { + .eraseblocks = { {2 * 1024 * 1024, 1} }, + .block_erase = spi_block_erase_c7, + } + }, + /* TODO: Read/write 2nd status register */ + .status_register = &gd25q10_20_40_80_sr, + .unlock = spi_disable_blockprotect_bp4_srwd, + .write = spi_chip_write_256, + .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ + .voltage = {2700, 3600}, + }, + + { + .vendor = "GigaDevice", + .name = "GD25Q16B", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25Q16, .total_size = 2048, .page_size = 256, /* OTP: 1024B total, 256B reserved; read 0x48; write 0x42, erase 0x44 (B version only) */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_OK_PREW, .probe = probe_spi_rdid, .probe_timing = TIMING_ZERO, .block_erasers = { { @@ -6080,28 +6166,29 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 64} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp4_srwd, - .unlock = spi_disable_blockprotect_bp4_srwd, /* TODO: 2nd status reg (read with 0x35) */ + /* TODO: Read/write 2nd status register */ + .status_register = &gd25q16_32_64b_sr, + .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {2700, 3600}, },
{ .vendor = "GigaDevice", .name = "GD25Q32(B)", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25Q32, .total_size = 4096, .page_size = 256, @@ -7515,53 +7602,88 @@ const struct flashchip flashchips[] = { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, }, }, .printlock = spi_prettyprint_status_register_bp3_srwd, /* MX25L1605A bp2 only */ .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) supported (MX25L1608E supports dual-I/O read) */ .voltage = {2700, 3600}, },
{ .vendor = "Macronix", - .name = "MX25L1605D/MX25L1608D/MX25L1673E", + .name = "MX25L1605D/MX25L1608D", + .bustype = BUS_SPI, + .manufacture_id = MACRONIX_ID, + .model_id = MACRONIX_MX25L1605, + .total_size = 2048, + .page_size = 256, + .feature_bits = FEATURE_WRSR_WREN, + .tested = TEST_OK_PREW, + .probe = probe_spi_rdid, + .probe_timing = TIMING_ZERO, + .block_erasers = + { + { + .eraseblocks = { {4 * 1024, 512} }, + .block_erase = spi_block_erase_20, + }, { + .eraseblocks = { {64 * 1024, 32} }, + .block_erase = spi_block_erase_d8, + }, { + .eraseblocks = { {2 * 1024 * 1024, 1} }, + .block_erase = spi_block_erase_60, + }, { + .eraseblocks = { {2 * 1024 * 1024, 1} }, + .block_erase = spi_block_erase_c7, + }, + }, + .status_register = &mx25lx5d_sr, + .unlock = spi_disable_blockprotect_bp3_srwd, + .write = spi_chip_write_256, + .read = spi_chip_read, /* Fast read (0x0B), dual I/O supported */ + .voltage = {2700, 3600}, + }, + + { + .vendor = "Macronix", + .name = "MX25L1673E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L1605, .total_size = 2048, .page_size = 256, .feature_bits = FEATURE_WRSR_WREN, .tested = TEST_OK_PREW, .probe = probe_spi_rdid, .probe_timing = TIMING_ZERO, .block_erasers = { { .eraseblocks = { {4 * 1024, 512} }, .block_erase = spi_block_erase_20, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, }, }, - .printlock = spi_prettyprint_status_register_bp3_srwd, /* bit6: Continuously Program (CP) mode, for 73E is quad enable */ + .status_register = &mx25lx65e_sr, .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B), dual I/O supported */ .voltage = {2700, 3600}, },
{ .vendor = "Macronix", .name = "MX25L1635D", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L1635D, .total_size = 2048, @@ -7684,27 +7806,27 @@ const struct flashchip flashchips[] = { .eraseblocks = { {4 * 1024, 1024} }, .block_erase = spi_block_erase_20, }, { .eraseblocks = { {64 * 1024, 64} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {4 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {4 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, }, }, - .printlock = spi_prettyprint_status_register_bp3_srwd, /* bit6: continuously program mode */ + .status_register = &mx25lx5d_sr, .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and dual I/O supported */ .voltage = {2700, 3600}, },
{ .vendor = "Macronix", .name = "MX25L3206E/MX25L3208E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L3205, .total_size = 4096, @@ -7870,27 +7992,27 @@ const struct flashchip flashchips[] = { .eraseblocks = { {4 * 1024, 2048} }, .block_erase = spi_block_erase_20, }, { .eraseblocks = { {64 * 1024, 128} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp3_srwd, /* bit6: continuously program mode */ + .status_register = &mx25lx5d_sr, .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B), dual I/O read (0xBB) supported */ .voltage = {2700, 3600}, },
{ .vendor = "Macronix", .name = "MX25L6406E/MX25L6408E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L6405, .total_size = 8192, @@ -7910,27 +8032,27 @@ const struct flashchip flashchips[] = { .eraseblocks = { {64 * 1024, 128} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 128} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp3_srwd, + .status_register = &mx25l64xe_sr, .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B), dual I/O read supported */ .voltage = {2700, 3600}, },
{ .vendor = "Macronix", .name = "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L6405, .total_size = 8192, @@ -7950,27 +8072,32 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 256} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 128} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - .printlock = spi_prettyprint_status_register_bp3_srwd, /* bit6 is quad enable */ + /* FIXME: MX25L6473E has an additional configuration register (which behaves like + * a 2nd status register). + * FIXME: Datasheet for MX25L6473E indicates bit 7 is RESV (instead + * of SRWD), but similar chips have SRWD. + */ + .status_register = &mx25lx65e_sr, .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {2700, 3600}, },
{ .vendor = "Macronix", .name = "MX25L12805D", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L12805D, .total_size = 16384, @@ -8026,27 +8153,27 @@ const struct flashchip flashchips[] = { .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 256} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {16 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {16 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, /* TODO: security register and SBLK/SBULK; MX25L12835F: configuration register */ - .printlock = spi_prettyprint_status_register_bp3_srwd, /* bit6 is quad enable */ + .status_register = &mx25lx65e_sr, .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) supported */ .voltage = {2700, 3600}, },
{ .vendor = "Macronix", .name = "MX25U1635E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25U1635E, .total_size = 2048, diff --git a/spi25_statusreg.h b/spi25_statusreg.h index b082f45..8bfffa0 100644 --- a/spi25_statusreg.h +++ b/spi25_statusreg.h @@ -66,14 +66,50 @@ enum wp_mode {
/* Status register is protected and can not be written to until next * power down/up cycle, post which status register will be unlocked * and can be written to after a WREN. */ WP_MODE_POWER_CYCLE,
/* Status register is permanently protected and cannot be written to. */ WP_MODE_PERMANENT, };
/* Describes corresponding bits from enum status_register_bit */ extern char *statreg_bit_desc[][2];
+/* === Single status register === */ +/* === AMIC === */ +extern struct status_register a25l080_sr; + +/* === Macronix === */ +extern struct status_register mx25l64xe_sr; +extern struct status_register mx25lx5d_sr; +extern struct status_register mx25lx65e_sr; + +/* === Double status registers === */ +/* === AMIC === */ +extern struct status_register a25lq16_32a_sr; +extern struct status_register a25l032_sr; + +/* === GigaDevice === */ +extern struct status_register gd25lq_sr; +extern struct status_register gd25q16_32_64b_sr; +extern struct status_register gd25q10_20_40_80_sr; +extern struct status_register gd25vq16_80c_q16_40c_sr; +extern struct status_register gd25vq21_41b_q21_q41b_sr; +extern struct status_register gd25q80b_128_sr; + +/* === Winbond === */ +extern struct status_register w25q80_16_32_sr; +extern struct status_register w25q40bl_64fv_sr; + +/* === Triple status registers === */ +/* === GigaDevice === */ +extern struct status_register gd25lq05_10_20b_sr; +extern struct status_register gd25q32_64c_sr; +extern struct status_register gd25q127c_sr; +extern struct status_register gd25q128c_sr; + +/* === Winbond === */ +extern struct status_register w25q128fw_sr; + #endif /* !__SPI25_STATUSREG_H__ */ diff --git a/statusreg_layouts.c b/statusreg_layouts.c new file mode 100644 index 0000000..f4275af --- /dev/null +++ b/statusreg_layouts.c @@ -0,0 +1,320 @@ +/* + * 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" + +/* + * struct status_register { + * enum status_register_bit layout[MAX_STATUS_REGISTERS + 1][8]; + * uint8_t (*read) (struct flashctx *flash, enum status_register_num SRn); + * int (*write) (struct flashctx *flash, enum status_register_num SRn, uint8_t status); + * int (*print) (struct flashctx *flash, enum status_register_num SRn); + * enum wp_mode (*get_wp_mode) (struct flashctx *flash); + * int (*set_wp_mode) (struct flashctx *flash, enum wp_mode wp_mode); + * int (*print_wp_mode) (struct flashctx *flash); + * }; + */ + +/* === Single status register === */ +/* === AMIC === */ +/* A25L080 */ +struct status_register a25l080_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, RESV, RESV, SRP0 }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; +/* === Macronix === */ +/* MX25L6408E, MX25L6406E */ +struct status_register mx25l64xe_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, RESV, SRP0 }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* MX25L1605D, MX25L3205D, MX25L6405D, MX25L1608D, MX25L3208D, + * MX25L6408D */ +// TODO(hatim): Add support for MX25L6408D +struct status_register mx25lx5d_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, CP, SRP0 }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* MX25L6436E, MX25L6445E, MX25L6465E, MX25L12865E, MX25L12845E, + * MX25L12835F, MX25L1673E + * FIXME: MX25L12845E, MX25L12835F (These two chips have + * a configuration register that behaves like a 2nd status + * register.) + */ +struct status_register mx25lx65e_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, QE, SRP0 }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* === Double status registers === */ +/* === AMIC === */ +/* A25LQ16, A25LQ32A */ +struct status_register a25lq16_32a_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, TB, SEC, SRP0 }, + { SRP1, QE, APT, RESV, RESV, RESV, CMP, SUS }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* A25L032 */ +struct status_register a25l032_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, TB, SEC, SRP0 }, + { SRP1, RESV, APT, RESV, RESV, RESV, CMP, RESV }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* === GigaDevice === */ +/* GD25LQ16, GD25LQ40, GD25LQ80B, GD25LQ40B, GD25LQ64C, GD25LQ80, + * GD25LQ128C, GD25LQ32C */ +// TODO(hatim): Add support for GD25LQ32C, GD25LQ80B, GD25LQ40B, GD25LQ64C, GD25LQ32C +struct status_register gd25lq_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, SUS2, LB1, LB2, LB3, CMP, SUS1 }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25Q16B, GD25Q32B, GD25Q64B */ +struct status_register gd25q16_32_64b_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { RESV, QE, LB1, RESV, RESV, RESV, CMP, SUS }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25Q10, GD25Q16, GD25Q20, GD25Q40, GD25Q80 */ +struct status_register gd25q10_20_40_80_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, RESV, RESV, RESV, RESV, RESV, RESV }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25VQ16C, GD25VQ80C, GD25Q16C, GD25Q40C */ +struct status_register gd25vq16_80c_q16_40c_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, LB1, RESV, RESV, HPF, CMP, SUS }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25VQ21B, GD25VQ41B, GD25Q21B, GD25Q41B */ +struct status_register gd25vq21_41b_q21_q41b_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, HPF, LB1, LB2, LB3, CMP, SUS }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25Q80B, GD25Q128 */ +struct status_register gd25q80b_128_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, LB1, RESV, RESV, RESV, CMP, SUS }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* === Winbond === */ +/* W25Q80, W25Q16, W25Q32 */ +struct status_register w25q80_16_32_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, TB, SEC, SRP0 }, + { SRP1, QE, RESV, RESV, RESV, RESV, RESV, RESV }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* W25Q40BL, W25Q64FV */ +struct status_register w25q40bl_64fv_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, TB, SEC, SRP0 }, + { SRP1, QE, RESV, LB1, LB2, LB3, CMP, SUS }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* === Triple status registers === */ +/* === GigaDevice === */ +/* GD25LQ05B, GD25LQ10B, GD25LQ20B */ +struct status_register gd25lq05_10_20b_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, SUS2, LB1, LB2, LB3, CMP, SUS1 }, + { RESV, RESV, RESV, RESV, HPF, RESV, RESV, RESV }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25Q32C, GD25Q64C */ +struct status_register gd25q32_64c_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, SUS2, LB1, LB2, LB3, CMP, SUS1 }, + { RESV, RESV, RESV, RESV, HPF, DRV0, DRV1, RESV }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25Q127C */ +struct status_register gd25q127c_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, SUS2, LB1, LB2, LB3, CMP, SUS1 }, + { RESV, RESV, WPS, RESV, RESV, DRV0, DRV1, RST }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* GD25Q128C */ +struct status_register gd25q128c_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, BP3, BP4, SRP0 }, + { SRP1, QE, SUS2, LB1, LB2, LB3, CMP, SUS1 }, + { RESV, RESV, WPS, RESV, RESV, DRV0, DRV1, RST }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +}; + +/* === Winbond === */ +/* W25Q128FW */ +struct status_register w25q128fw_sr = { + .layout = { + { WIP, WEL, BP0, BP1, BP2, TB, SEC, SRP0 }, + { SRP1, QE, RESV, LB1, LB2, LB3, CMP, SUS }, + { RESV, RESV, WPS, RESV, RESV, DRV0, DRV1, RST }, + }, + .read = &spi_read_status_register_generic, + .write = &spi_write_status_register_generic, + .print = &spi_prettyprint_status_register_generic, + .print_wp_mode = &spi_prettyprint_status_register_wp_generic, + .get_wp_mode = &get_wp_mode_generic, + .set_wp_mode = &set_wp_mode_generic, +};
- struct wp defines added for the following chips (21 in total) - - AMIC(4) : A25LQ032, A25LQ32A, A25L080, A25LQ16 - Macronix(12) : MX25L1605D, MX25L1608D, MX25L1673E, MX25L6406E, MX25L6408E, MX25L6405D, MX25L3205D, MX25L3208D, MX25L6436E, MX25L6445E, MX25L6465E, MX25L6473E - GigaDevice(5) : GD25LQ40, GD25LQ80, GD25LQ16, GD25Q16, GD25Q16B - All of the above support new infrastructure (in flashchips.c) (WIP) - 6 unique struct definitions were required to represent all of the above chips
Signed-off-by: Hatim Kanchwala hatim@hatimak.me --- Makefile | 6 +- chipdrivers.h | 1 + flashchips.c | 39 +++++------- writeprotect.c | 19 ++++++ writeprotect.h | 7 +++ writeprotect_layouts.c | 166 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 212 insertions(+), 26 deletions(-) create mode 100644 writeprotect_layouts.c
diff --git a/Makefile b/Makefile index ed4979e..c274e79 100644 --- a/Makefile +++ b/Makefile @@ -502,29 +502,29 @@ else override CONFIG_SATAMV = no 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 \ - writeprotect.o statusreg_layouts.o opaque.o sfdp.o en29lv640b.o at45db.o + sst28sf040.o 82802ab.o sst49lfxxxc.o sst_fwhub.o flashchips.o spi.o \ + spi25.o spi25_statusreg.o writeprotect.o statusreg_layouts.o \ + writeprotect_layouts.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 c60aff8..d3b27cc 100644 --- a/chipdrivers.h +++ b/chipdrivers.h @@ -116,26 +116,27 @@ 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); +struct range *a25l032_range_table(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); diff --git a/flashchips.c b/flashchips.c index 0afd297..725a9e2 100644 --- a/flashchips.c +++ b/flashchips.c @@ -16,26 +16,27 @@ * 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 "flash.h" #include "flashchips.h" #include "chipdrivers.h" #include "spi25_statusreg.h" +#include "writeprotect.h"
/** * List of supported flash chips. * * Please keep the list sorted by vendor name and chip family, so that the output of 'flashrom -L' is roughly * alphabetically sorted. Within families keep them in order of density. */ const struct flashchip flashchips[] = {
/* * .vendor = Vendor name * .name = Chip name * .bustype = Supported flash bus types (Parallel, LPC...) @@ -1156,32 +1157,31 @@ const struct flashchip flashchips[] = { .block_erasers = { { .eraseblocks = { { 4 * 1024, 256 } }, .block_erase = spi_block_erase_20, }, { .eraseblocks = { { 64 * 1024, 16 } }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { { 1024 * 1024, 1 } }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register */ .status_register = &a25l080_sr, - .unlock = spi_disable_blockprotect, .write = spi_chip_write_256, .read = spi_chip_read, .voltage = {2700, 3600}, + .wp = &gd25_a25l080_q16_32a_wp, },
{ .vendor = "AMIC", .name = "A25L016", .bustype = BUS_SPI, .manufacture_id = AMIC_ID_NOPREFIX, .model_id = AMIC_A25L016, .total_size = 2048, .page_size = 256, .feature_bits = FEATURE_WRSR_WREN, .tested = TEST_UNTESTED, .probe = probe_spi_rdid, @@ -1267,32 +1267,31 @@ const struct flashchip flashchips[] = { .eraseblocks = { { 64 * 1024, 32 } }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { { 64 * 1024, 32 } }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { { 2048 * 1024, 1 } }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { { 2048 * 1024, 1 } }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register */ .status_register = &a25lq16_32a_sr, - .unlock = spi_disable_blockprotect_bp2_srwd, .write = spi_chip_write_256, .read = spi_chip_read, .voltage = {2700, 3600}, + .wp = &gd25_a25l080_q16_32a_wp, },
{ .vendor = "AMIC", .name = "A25LQ032", .bustype = BUS_SPI, .manufacture_id = AMIC_ID_NOPREFIX, .model_id = AMIC_A25LQ032, .total_size = 4096, .page_size = 256, /* A25LQ32A supports SFDP */ /* OTP: 64B total; read 0x4B, 0x48; write 0x42 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, @@ -1308,32 +1307,31 @@ const struct flashchip flashchips[] = { .eraseblocks = { { 64 * 1024, 64 } }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { { 64 * 1024, 64 } }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { { 4096 * 1024, 1 } }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { { 4096 * 1024, 1 } }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register */ .status_register = &a25l032_sr, - .unlock = spi_disable_blockprotect_bp2_srwd, .write = spi_chip_write_256, .read = spi_chip_read, .voltage = {2700, 3600}, + .wp = &a25l032_32a_wp, },
{ .vendor = "AMIC", .name = "A25LQ32A", .bustype = BUS_SPI, .manufacture_id = AMIC_ID_NOPREFIX, .model_id = AMIC_A25LQ032, .total_size = 4096, .page_size = 256, /* A25LQ32A supports SFDP */ /* OTP: 64B total; read 0x4B, 0x48; write 0x42 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, @@ -1350,30 +1348,30 @@ const struct flashchip flashchips[] = { .block_erase = spi_block_erase_52, }, { .eraseblocks = { { 64 * 1024, 64 } }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { { 4096 * 1024, 1 } }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { { 4096 * 1024, 1 } }, .block_erase = spi_block_erase_c7, } }, .status_register = &a25lq16_32a_sr, - .unlock = spi_disable_blockprotect_bp2_srwd, /* TODO: 2nd status reg (read with 0x35) */ .write = spi_chip_write_256, .read = spi_chip_read, .voltage = {2700, 3600}, + .wp = &a25l032_32a_wp, },
{ .vendor = "AMIC", .name = "A25LQ64", .bustype = BUS_SPI, .manufacture_id = AMIC_ID_NOPREFIX, .model_id = AMIC_A25LQ64, .total_size = 8192, .page_size = 256, /* supports SFDP */ /* OTP: 512B total; enter 0xB1, exit 0xC1 */ /* QPI enable 0x35, disable 0xF5 */ @@ -5704,32 +5702,31 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 16} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 8} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {512 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {512 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register */ .status_register = &gd25lq_sr, - .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {1695, 1950}, + .wp = &gd25_a25l080_q16_32a_wp, },
{ .vendor = "GigaDevice", .name = "GD25LQ80", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25LQ80, .total_size = 1024, .page_size = 256, /* OTP: 1024B total, 256B reserved; read 0x48; write 0x42, erase 0x44 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_UNTESTED, @@ -5744,32 +5741,31 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 32} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 16} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {1 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {1 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register*/ .status_register = &gd25lq_sr, - .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {1695, 1950}, + .wp = &gd25_a25l080_q16_32a_wp, },
{ .vendor = "GigaDevice", .name = "GD25LQ16", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25LQ16, .total_size = 2048, .page_size = 256, /* OTP: 1024B total, 256B reserved; read 0x48; write 0x42, erase 0x44 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_UNTESTED, @@ -5784,32 +5780,31 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 64} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register */ .status_register = &gd25lq_sr, - .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {1695, 1950}, + .wp = &gd25_a25l080_q16_32a_wp, },
{ .vendor = "GigaDevice", .name = "GD25LQ32", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25LQ32, .total_size = 4096, .page_size = 256, /* OTP: 1024B total, 256B reserved; read 0x48; write 0x42, erase 0x44 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_OK_PREW, @@ -6126,32 +6121,31 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 64} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register */ .status_register = &gd25q10_20_40_80_sr, - .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {2700, 3600}, + .wp = &gd25_a25l080_q16_32a_wp, },
{ .vendor = "GigaDevice", .name = "GD25Q16B", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25Q16, .total_size = 2048, .page_size = 256, /* OTP: 1024B total, 256B reserved; read 0x48; write 0x42, erase 0x44 (B version only) */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_OK_PREW, @@ -6166,32 +6160,31 @@ const struct flashchip flashchips[] = { .eraseblocks = { {32 * 1024, 64} }, .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, - /* TODO: Read/write 2nd status register */ .status_register = &gd25q16_32_64b_sr, - .unlock = spi_disable_blockprotect_bp4_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {2700, 3600}, + .wp = &gd25_a25l080_q16_32a_wp, },
{ .vendor = "GigaDevice", .name = "GD25Q32(B)", .bustype = BUS_SPI, .manufacture_id = GIGADEVICE_ID, .model_id = GIGADEVICE_GD25Q32, .total_size = 4096, .page_size = 256, /* OTP: 1024B total, 256B reserved; read 0x48; write 0x42, erase 0x44 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_OK_PREW, @@ -7629,30 +7622,30 @@ const struct flashchip flashchips[] = { .block_erase = spi_block_erase_20, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, }, }, .status_register = &mx25lx5d_sr, - .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B), dual I/O supported */ .voltage = {2700, 3600}, + .wp = &mx25l16xd_wp, },
{ .vendor = "Macronix", .name = "MX25L1673E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L1605, .total_size = 2048, .page_size = 256, .feature_bits = FEATURE_WRSR_WREN, .tested = TEST_OK_PREW, .probe = probe_spi_rdid, @@ -7664,30 +7657,30 @@ const struct flashchip flashchips[] = { .block_erase = spi_block_erase_20, }, { .eraseblocks = { {64 * 1024, 32} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {2 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, }, }, .status_register = &mx25lx65e_sr, - .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B), dual I/O supported */ .voltage = {2700, 3600}, + .wp = &mx25l16xd_wp, },
{ .vendor = "Macronix", .name = "MX25L1635D", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L1635D, .total_size = 2048, .page_size = 256, /* OTP: 64B total; enter 0xB1, exit 0xC1 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_UNTESTED, @@ -7807,30 +7800,30 @@ const struct flashchip flashchips[] = { .block_erase = spi_block_erase_20, }, { .eraseblocks = { {64 * 1024, 64} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {4 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {4 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, }, }, .status_register = &mx25lx5d_sr, - .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and dual I/O supported */ .voltage = {2700, 3600}, + .wp = &mx25lx5d_wp, },
{ .vendor = "Macronix", .name = "MX25L3206E/MX25L3208E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L3205, .total_size = 4096, .page_size = 256, /* OTP: 64B total; enter 0xB1, exit 0xC1 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_OK_PREW, @@ -7993,30 +7986,30 @@ const struct flashchip flashchips[] = { .block_erase = spi_block_erase_20, }, { .eraseblocks = { {64 * 1024, 128} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, .status_register = &mx25lx5d_sr, - .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B), dual I/O read (0xBB) supported */ .voltage = {2700, 3600}, + .wp = &mx25l6405d_wp, },
{ .vendor = "Macronix", .name = "MX25L6406E/MX25L6408E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L6405, .total_size = 8192, .page_size = 256, /* MX25L6406E supports SFDP */ /* OTP: 06E 64B total; enter 0xB1, exit 0xC1 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, @@ -8033,30 +8026,30 @@ const struct flashchip flashchips[] = { .block_erase = spi_block_erase_52, }, { .eraseblocks = { {64 * 1024, 128} }, .block_erase = spi_block_erase_d8, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, .status_register = &mx25l64xe_sr, - .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B), dual I/O read supported */ .voltage = {2700, 3600}, + .wp = &mx25l6405d_wp, },
{ .vendor = "Macronix", .name = "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L6405, .total_size = 8192, .page_size = 256, /* supports SFDP */ /* OTP: 512B total; enter 0xB1, exit 0xC1 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, @@ -8078,30 +8071,30 @@ const struct flashchip flashchips[] = { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_60, }, { .eraseblocks = { {8 * 1024 * 1024, 1} }, .block_erase = spi_block_erase_c7, } }, /* FIXME: MX25L6473E has an additional configuration register (which behaves like * a 2nd status register). * FIXME: Datasheet for MX25L6473E indicates bit 7 is RESV (instead * of SRWD), but similar chips have SRWD. */ .status_register = &mx25lx65e_sr, - .unlock = spi_disable_blockprotect_bp3_srwd, .write = spi_chip_write_256, .read = spi_chip_read, /* Fast read (0x0B) and multi I/O supported */ .voltage = {2700, 3600}, + .wp = &mx25lx65e_wp, },
{ .vendor = "Macronix", .name = "MX25L12805D", .bustype = BUS_SPI, .manufacture_id = MACRONIX_ID, .model_id = MACRONIX_MX25L12805D, .total_size = 16384, .page_size = 256, /* OTP: 64B total; enter 0xB1, exit 0xC1 */ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP, .tested = TEST_OK_PREW, diff --git a/writeprotect.c b/writeprotect.c index e256ae4..3ea6add 100644 --- a/writeprotect.c +++ b/writeprotect.c @@ -374,13 +374,32 @@ int disable_generic(struct flashctx *flash) { 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; } + +/* === Chip specific range_table generators === */ +/* === AMIC === */ +/* A25L032 */ +struct range *a25l032_range_table(struct flashctx *flash) { + struct range *table = sec_block_range_pattern(flash); + /* CMP=0 */ + table[0x16].start = 0x3f0000; + table[0x16].len = 64; + table[0x1e].start = 0x000000; + table[0x1e].len = 64; + /* CMP=1 */ + table[1 << 5 | 0x16].start = 0x000000; + table[1 << 5 | 0x16].len = 4032; + table[1 << 5 | 0x1e].start = 0x010000; + table[1 << 5 | 0x1e].len = 4032; + + return table; +} diff --git a/writeprotect.h b/writeprotect.h index 41d9cf0..41dbc7c 100644 --- a/writeprotect.h +++ b/writeprotect.h @@ -20,14 +20,21 @@
#ifndef __WRITEPROTECT_H__ #define __WRITEPROTECT_H__ 1
#include "flash.h"
#define LEN_RANGES 64
struct range { uint32_t start; uint32_t len; /* in kB */ };
+extern struct wp a25l032_32a_wp; +extern struct wp gd25_a25l080_q16_32a_wp; +extern struct wp mx25l16xd_wp; +extern struct wp mx25l6405d_wp; +extern struct wp mx25lx5d_wp; +extern struct wp mx25lx65e_wp; + #endif /* !__WRITEPROTECT_H__ */ diff --git a/writeprotect_layouts.c b/writeprotect_layouts.c new file mode 100644 index 0000000..f5ac2ec --- /dev/null +++ b/writeprotect_layouts.c @@ -0,0 +1,166 @@ +/* + * 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 "writeprotect.h" + +/* + * struct wp { + * struct range *ranges; + * struct range *(*range_table) (struct flashctx *flash); + * uint32_t (*bp_bitmask) (struct flashctx *flash); + * int (*set_range) (struct flashctx *flash, uint32_t start, uint32_t len); + * int (*disable) (struct flashctx *flash); + * int (*print_table) (struct flashctx *flash); + * }; + */ + +/* A25LQ032, A25LQ32A */ +struct wp a25l032_32a_wp = { + .range_table = &a25l032_range_table, + .bp_bitmask = &bp_bitmask_generic, + .print_table = &print_table_generic, + .set_range = &set_range_generic, + .disable = &disable_generic, +}; + +/* A25L080, A25LQ16, GD25LQ40, GD25LQ80, GD25LQ16, GD25Q16, GD25Q16B */ +struct wp gd25_a25l080_q16_32a_wp = { + .range_table = &sec_block_range_pattern, + .bp_bitmask = &bp_bitmask_generic, + .print_table = &print_table_generic, + .set_range = &set_range_generic, + .disable = &disable_generic, +}; + +/* MX25L1605D, MX25L1608D, MX25L1673E */ +struct wp mx25l16xd_wp = { + .ranges = (struct range[]){ + /* BP3 effectively acts as CMP bit, + * BP[0..2] function normally. */ + { 0x000000, 0 }, + { 0x1f0000, 64 }, + { 0x1e0000, 128 }, + { 0x1c0000, 256 }, + { 0x180000, 512 }, + { 0x100000, 1024 }, + { 0x000000, 2048 }, + { 0x000000, 2048 }, + + { 0x000000, 2048 }, + { 0x000000, 2048 }, + { 0x000000, 1024 }, + { 0x000000, 1536 }, + { 0x000000, 1792 }, + { 0x000000, 1920 }, + { 0x000000, 1984 }, + { 0x000000, 2048 }, + }, + .bp_bitmask = &bp_bitmask_generic, + .print_table = &print_table_generic, + .set_range = &set_range_generic, + .disable = &disable_generic, +}; + +/* MX25L6406E, MX25L6408E, MX25L6405D */ +struct wp mx25l6405d_wp = { + /* BP3 effectively acts as CMP bit, + * BP[0..2] function normally. */ + .ranges = (struct range[]){ + { 0x000000, 0 }, + { 0x7e0000, 128 }, + { 0x7c0000, 256 }, + { 0x780000, 512 }, + { 0x700000, 1024 }, + { 0x600000, 2048 }, + { 0x400000, 4096 }, + { 0x000000, 8192 }, + + { 0x000000, 8192 }, + { 0x000000, 4096 }, + { 0x000000, 6144 }, + { 0x000000, 7168 }, + { 0x000000, 7680 }, + { 0x000000, 7936 }, + { 0x000000, 8064 }, + { 0x000000, 8192 }, + }, + .bp_bitmask = &bp_bitmask_generic, + .print_table = &print_table_generic, + .set_range = &set_range_generic, + .disable = &disable_generic, +}; + +/* MX25L3205D, MX25L3208D */ +struct wp mx25lx5d_wp = { + .ranges = (struct range[]){ + /* BP3 effectively acts as CMP bit, + * BP[0..2] function normally. */ + { 0x000000, 0 }, + { 0x3f0000, 64 }, + { 0x3e0000, 128 }, + { 0x3c0000, 256 }, + { 0x380000, 512 }, + { 0x300000, 1024 }, + { 0x200000, 2048 }, + { 0x000000, 4096 }, + + { 0x000000, 4096 }, + { 0x000000, 2048 }, + { 0x000000, 3072 }, + { 0x000000, 3584 }, + { 0x000000, 3840 }, + { 0x000000, 3968 }, + { 0x000000, 4032 }, + { 0x000000, 4096 }, + }, + .bp_bitmask = &bp_bitmask_generic, + .print_table = &print_table_generic, + .set_range = &set_range_generic, + .disable = &disable_generic, +}; + +/* MX25L6436E, MX25L6445E, MX25L6465E, MX25L6473E */ +struct wp mx25lx65e_wp = { + .ranges = (struct range[]){ + { 0x000000, 0 }, + { 0x7e0000, 128 }, + { 0x7c0000, 256 }, + { 0x780000, 512 }, + { 0x700000, 1024 }, + { 0x600000, 2048 }, + { 0x400000, 4096 }, + { 0x000000, 8192 }, + + { 0x000000, 8192 }, + { 0x000000, 8192 }, + { 0x000000, 8192 }, + { 0x000000, 8192 }, + { 0x000000, 8192 }, + { 0x000000, 8192 }, + { 0x000000, 8192 }, + { 0x000000, 8192 }, + }, + .bp_bitmask = &bp_bitmask_generic, + .print_table = &print_table_generic, + .set_range = &set_range_generic, + .disable = &disable_generic, +};
- New infrastructure is used to read status register (for chips that have support for struct status_register) throughout spi25.c. - New infrastructure is used to prettyprint status register and write protection mode of status register (for chips that have support for struct status_register) in flashrom.c. - New disable from access protection infrastructure is used (for chips that have support for struct wp) in flashrom.c
Signed-off-by: Hatim Kanchwala hatim@hatimak.me --- flashrom.c | 17 +++++-- spi25.c | 164 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 145 insertions(+), 36 deletions(-)
diff --git a/flashrom.c b/flashrom.c index 25e53f2..3e19cd6 100644 --- a/flashrom.c +++ b/flashrom.c @@ -26,26 +26,27 @@ #ifndef __LIBPAYLOAD__ #include <fcntl.h> #include <sys/stat.h> #endif #include <string.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <ctype.h> #include <getopt.h> #if HAVE_UTSNAME == 1 #include <sys/utsname.h> #endif +#include "chipdrivers.h" #include "flash.h" #include "flashchips.h" #include "programmer.h" #include "hwaccess.h"
const char flashrom_version[] = FLASHROM_VERSION; const char *chip_to_probe = NULL;
static enum programmer programmer = PROGRAMMER_INVALID; static const char *programmer_param = NULL;
/* * Programmers supporting multiple buses can have differing size limits on @@ -1250,29 +1251,37 @@ notfound: msg_cinfo("%s %s flash chip "%s" (%d kB, %s) ", force ? "Assuming" : "Found", flash->chip->vendor, flash->chip->name, flash->chip->total_size, tmp); free(tmp); #if CONFIG_INTERNAL == 1 if (programmer_table[programmer].map_flash_region == physmap) msg_cinfo("mapped at physical address 0x%0*" PRIxPTR ".\n", PRIxPTR_WIDTH, flash->physical_memory); else #endif msg_cinfo("on %s.\n", programmer_table[programmer].name);
/* Flash registers may more likely not be mapped if the chip was forced. * Lock info may be stored in registers, so avoid lock info printing. */ - if (!force) - if (flash->chip->printlock) + if (!force) { + if (flash->chip->status_register) { + for (enum status_register_num SRn = SR1; SRn <= top_status_register(flash); SRn++) + flash->chip->status_register->print(flash, SRn); + flash->chip->status_register->print_wp_mode(flash); + if (flash->chip->wp) + print_range_generic(flash); + } else if (flash->chip->printlock) { flash->chip->printlock(flash); + } + }
/* Get out of the way for later runs. */ unmap_flash(flash);
/* Return position of matching chip. */ return chip - flashchips; }
int read_buf_from_file(unsigned char *buf, unsigned long size, const char *filename) { #ifdef __LIBPAYLOAD__ msg_gerr("Error: No file I/O support in libpayload\n"); @@ -1988,27 +1997,29 @@ int doit(struct flashctx *flash, int force, const char *filename, int read_it, if (chip_safety_check(flash, force, read_it, write_it, erase_it, verify_it)) { msg_cerr("Aborting.\n"); return 1; }
if (normalize_romentries(flash)) { msg_cerr("Requested regions can not be handled. Aborting.\n"); return 1; }
/* Given the existence of read locks, we want to unlock for read, * erase and write. */ - if (flash->chip->unlock) + if (flash->chip->wp) + flash->chip->wp->disable(flash); + else if (flash->chip->unlock) flash->chip->unlock(flash);
if (read_it) { return read_flash_to_file(flash, filename); }
oldcontents = malloc(size); if (!oldcontents) { msg_gerr("Out of memory!\n"); exit(1); } /* Assume worst case: All bits are 0. */ memset(oldcontents, 0x00, size); diff --git a/spi25.c b/spi25.c index af4b6db..51db4c8 100644 --- a/spi25.c +++ b/spi25.c @@ -341,29 +341,35 @@ int spi_chip_erase_60(struct flashctx *flash) .readcnt = 0, .readarr = NULL, }}; result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution\n", __func__); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 1-85 s, so wait in 1 s steps. */ - /* FIXME: We assume spi_read_status_register will never fail. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(1000 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(1000 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(1000 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
int spi_chip_erase_62(struct flashctx *flash) { int result; struct spi_command cmds[] = { { .writecnt = JEDEC_WREN_OUTSIZE, .writearr = (const unsigned char[]){ JEDEC_WREN }, .readcnt = 0, .readarr = NULL, @@ -378,29 +384,35 @@ int spi_chip_erase_62(struct flashctx *flash) .readcnt = 0, .readarr = NULL, }}; result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution\n", __func__); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 2-5 s, so wait in 100 ms steps. */ - /* FIXME: We assume spi_read_status_register will never fail. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(100 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
int spi_chip_erase_c7(struct flashctx *flash) { int result; struct spi_command cmds[] = { { .writecnt = JEDEC_WREN_OUTSIZE, .writearr = (const unsigned char[]){ JEDEC_WREN }, .readcnt = 0, .readarr = NULL, @@ -414,29 +426,35 @@ int spi_chip_erase_c7(struct flashctx *flash) .writearr = NULL, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution\n", __func__); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 1-85 s, so wait in 1 s steps. */ - /* FIXME: We assume spi_read_status_register will never fail. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(1000 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(1000 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(1000 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
int spi_block_erase_52(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; struct spi_command cmds[] = { { .writecnt = JEDEC_WREN_OUTSIZE, .writearr = (const unsigned char[]){ JEDEC_WREN }, .readcnt = 0, @@ -457,28 +475,35 @@ int spi_block_erase_52(struct flashctx *flash, unsigned int addr, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 100-4000 ms, so wait in 100 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(100 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
/* Block size is usually * 32M (one die) for Micron */ int spi_block_erase_c4(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; struct spi_command cmds[] = { { .writecnt = JEDEC_WREN_OUTSIZE, @@ -500,28 +525,35 @@ int spi_block_erase_c4(struct flashctx *flash, unsigned int addr, unsigned int b .writearr = NULL, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 240-480 s, so wait in 500 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(500 * 1000 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(500 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(500 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
/* Block size is usually * 64k for Macronix * 32k for SST * 4-32k non-uniform for EON */ int spi_block_erase_d8(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; @@ -547,28 +579,35 @@ int spi_block_erase_d8(struct flashctx *flash, unsigned int addr, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 100-4000 ms, so wait in 100 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(100 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
/* Block size is usually * 4k for PMC */ int spi_block_erase_d7(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; struct spi_command cmds[] = { { @@ -592,28 +631,35 @@ int spi_block_erase_d7(struct flashctx *flash, unsigned int addr, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 100-4000 ms, so wait in 100 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(100 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(100 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
/* Page erase (usually 256B blocks) */ int spi_block_erase_db(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; struct spi_command cmds[] = { { .writecnt = JEDEC_WREN_OUTSIZE, .writearr = (const unsigned char[]){ JEDEC_WREN }, .readcnt = 0, @@ -633,28 +679,35 @@ int spi_block_erase_db(struct flashctx *flash, unsigned int addr, unsigned int b .writearr = NULL, .readcnt = 0, .readarr = NULL, } };
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; }
/* Wait until the Write-In-Progress bit is cleared. * This takes up to 20 ms usually (on worn out devices up to the 0.5s range), so wait in 1 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(1 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(1 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(1 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
/* Sector size is usually 4k, though Macronix eliteflash has 64k */ int spi_block_erase_20(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; struct spi_command cmds[] = { { .writecnt = JEDEC_WREN_OUTSIZE, .writearr = (const unsigned char[]){ JEDEC_WREN }, @@ -676,28 +729,35 @@ int spi_block_erase_20(struct flashctx *flash, unsigned int addr, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 15-800 ms, so wait in 10 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(10 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(10 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(10 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
int spi_block_erase_50(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; struct spi_command cmds[] = { { /* .writecnt = JEDEC_WREN_OUTSIZE, .writearr = (const unsigned char[]){ JEDEC_WREN }, .readcnt = 0, .readarr = NULL, @@ -716,28 +776,35 @@ int spi_block_erase_50(struct flashctx *flash, unsigned int addr, unsigned int b .writearr = NULL, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 10 ms, so wait in 1 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(1 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(1 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(1 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
int spi_block_erase_81(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { int result; struct spi_command cmds[] = { { /* .writecnt = JEDEC_WREN_OUTSIZE, .writearr = (const unsigned char[]){ JEDEC_WREN }, .readcnt = 0, .readarr = NULL, @@ -756,28 +823,35 @@ int spi_block_erase_81(struct flashctx *flash, unsigned int addr, unsigned int b .writearr = NULL, .readcnt = 0, .readarr = NULL, }};
result = spi_send_multicommand(flash, cmds); if (result) { msg_cerr("%s failed during command execution at address 0x%x\n", __func__, addr); return result; } /* Wait until the Write-In-Progress bit is cleared. * This usually takes 8 ms, so wait in 1 ms steps. */ - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(1 * 1000); + /* FIXME: We assume reading status register(s) will never fail. */ + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(1 * 1000); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(1 * 1000); + } /* FIXME: Check the status register for errors. */ return 0; }
int spi_block_erase_60(struct flashctx *flash, unsigned int addr, unsigned int blocklen) { if ((addr != 0) || (blocklen != flash->chip->total_size * 1024)) { msg_cerr("%s called with incorrect arguments\n", __func__); return -1; } return spi_chip_erase_60(flash); @@ -1004,54 +1078,66 @@ int spi_write_chunked(struct flashctx *flash, const uint8_t *buf, unsigned int s * page as well, the loop condition uses <=. */ for (i = start / page_size; i <= (start + len - 1) / page_size; i++) { /* Byte position of the first byte in the range in this page. */ /* starthere is an offset to the base address of the chip. */ starthere = max(start, i * page_size); /* Length of bytes in the range in this page. */ lenhere = min(start + len, (i + 1) * page_size) - starthere; for (j = 0; j < lenhere; j += chunksize) { towrite = min(chunksize, lenhere - j); rc = spi_nbyte_program(flash, starthere + j, buf + starthere - start + j, towrite); if (rc) break; - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(10); + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(10); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(10); + } } if (rc) break; }
return rc; }
/* * Program chip using byte programming. (SLOW!) * This is for chips which can only handle one byte writes * and for chips where memory mapped programming is impossible * (e.g. due to size constraints in IT87* for over 512 kB) */ /* real chunksize is 1, logical chunksize is 1 */ int spi_chip_write_1(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len) { unsigned int i; int result = 0;
for (i = start; i < start + len; i++) { result = spi_byte_program(flash, i, buf[i - start]); if (result) return 1; - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(10); + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(10); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(10); + } }
return 0; }
int default_spi_write_aai(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len) { uint32_t pos = start; int result; unsigned char cmd[JEDEC_AAI_WORD_PROGRAM_CONT_OUTSIZE] = { JEDEC_AAI_WORD_PROGRAM, }; struct spi_command cmds[] = { @@ -1120,43 +1206,55 @@ int default_spi_write_aai(struct flashctx *flash, const uint8_t *buf, unsigned i if (len % 2) { msg_cerr("%s: total write length not even! Please report a " "bug at flashrom@flashrom.org\n", __func__); /* Do not return an error for now. */ //return SPI_GENERIC_ERROR; }
result = spi_send_multicommand(flash, cmds); if (result != 0) { msg_cerr("%s failed during start command execution: %d\n", __func__, result); goto bailout; } - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(10); + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(10); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(10); + }
/* We already wrote 2 bytes in the multicommand step. */ pos += 2;
/* Are there at least two more bytes to write? */ while (pos < start + len - 1) { cmd[1] = buf[pos++ - start]; cmd[2] = buf[pos++ - start]; result = spi_send_command(flash, JEDEC_AAI_WORD_PROGRAM_CONT_OUTSIZE, 0, cmd, NULL); if (result != 0) { msg_cerr("%s failed during followup AAI command execution: %d\n", __func__, result); goto bailout; } - while (spi_read_status_register(flash) & SPI_SR_WIP) - programmer_delay(10); + // TODO(hatim): Switch to newer infrastructure completely after integration + if (flash->chip->status_register) { + while (flash->chip->status_register->read(flash, SR1) & SPI_SR_WIP) + programmer_delay(10); + } else { + while (spi_read_status_register(flash) & SPI_SR_WIP) + programmer_delay(10); + } }
/* Use WRDI to exit AAI mode. This needs to be done before issuing any other non-AAI command. */ result = spi_write_disable(flash); if (result != 0) { msg_cerr("%s failed to disable AAI mode.\n", __func__); return SPI_GENERIC_ERROR; }
/* Write remaining byte (if any). */ if (pos < start + len) { if (spi_chip_write_1(flash, buf + pos - start, pos, pos % 2)) return SPI_GENERIC_ERROR;
- Following new CLI options added - - print-status-reg : print detailed contents of status register(s) - print-wp-status : print write protection mode of status register(s) - wp-list : print list of write protection ranges - wp-enable : enable write protection of status register(s) to optionally supplied MODE argument - wp-disable : disable any write protection of status register(s) - wp-set-range : set write protection range to supplied range - Updated man page with new CLI options
Signed-off-by: Hatim Kanchwala hatim@hatimak.me --- cli_classic.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- flashrom.8.tmpl | 56 +++++++++++++++- 2 files changed, 250 insertions(+), 6 deletions(-)
diff --git a/cli_classic.c b/cli_classic.c index a2c2014..1170d33 100644 --- a/cli_classic.c +++ b/cli_classic.c @@ -17,133 +17,172 @@ * 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 <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <string.h> #include <stdlib.h> #include <getopt.h> +#include "chipdrivers.h" #include "flash.h" #include "flashchips.h" #include "programmer.h" +#include "writeprotect.h" + +enum LONGOPT_RETURN_VALUES { + /* Start after ASCII chars */ + LONGOPT_PRINT_STATUSREG = 256, + LONGOPT_PRINT_WP_STATUS, + LONGOPT_LIST_BP_RANGES, + LONGOPT_WP_ENABLE, + LONGOPT_WP_DISABLE, + LONGOPT_SET_BP_RANGE, +};
static void cli_classic_usage(const char *name) { printf("Please note that the command line interface for flashrom has changed between\n" "0.9.5 and 0.9.6 and will change again before flashrom 1.0.\n\n");
printf("Usage: %s [-h|-R|-L|" #if CONFIG_PRINT_WIKI == 1 "-z|" #endif "-p <programmername>[:<parameters>] [-c <chipname>]\n" "[-E|(-r|-w|-v) <file>] [-l <layoutfile> [-i <imagename>]...] [-n] [-f]]\n" - "[-V[V[V]]] [-o <logfile>]\n\n", name); + "[-V[V[V]]] [-o <logfile>] [--print-status-reg] [--print-wp-status]\n" + "[--wp-list] [--wp-enable[=<MODE>]] [--wp-disable]\n" + "[--wp-set-range start=<start>,len=<len>]\n\n", name);
printf(" -h | --help print this help text\n" " -R | --version print version (release)\n" " -r | --read <file> read flash and save to <file>\n" " -w | --write <file> write <file> to flash\n" " -v | --verify <file> verify flash against <file>\n" " -E | --erase erase flash memory\n" " -V | --verbose more verbose output\n" " -c | --chip <chipname> probe only for specified flash chip\n" " -f | --force force specific operations (see man page)\n" " -n | --noverify don't auto-verify\n" " -l | --layout <layoutfile> read ROM layout from <layoutfile>\n" " -i | --image <name> only flash image <name> from flash layout\n" " -o | --output <logfile> log output to <logfile>\n" " -L | --list-supported print supported devices\n" #if CONFIG_PRINT_WIKI == 1 " -z | --list-supported-wiki print supported devices in wiki syntax\n" #endif + " --print-status-reg print detailed contents of status register(s)\n" + " --print-wp-status print write protection mode of status register(s)\n" + " --wp-list print list of write protection ranges\n" + " --wp-enable[=MODE] enable write protection of status register(s)\n" + " MODE can be one of HARDWARE (default),\n" + " PERMANENT, POWER_CYCLE, SOFTWARE (see man page)\n" + " --wp-disable disable any write protection of status register(s)\n" + " --wp-set-range start=<start>,len=<len>\n" + " set write protection range (see man page)\n" " -p | --programmer <name>[:<param>] specify the programmer device. One of\n"); list_programmers_linebreak(4, 80, 0); printf(".\n\nYou can specify one of -h, -R, -L, " #if CONFIG_PRINT_WIKI == 1 "-z, " #endif "-E, -r, -w, -v or no operation.\n" "If no operation is specified, flashrom will only probe for flash chips.\n"); }
static void cli_classic_abort_usage(void) { printf("Please run "flashrom --help" for usage info.\n"); exit(1); }
+static void cli_statreg_wp_support(struct flashctx *flash) +{ + msg_ginfo("flashrom does not (yet) support write protection for chip "%s".\n" + "You could add support and send the patch to flashrom@flashrom.org\n", + flash->chip->name); +} + static int check_filename(char *filename, char *type) { if (!filename || (filename[0] == '\0')) { fprintf(stderr, "Error: No %s file specified.\n", type); return 1; } /* Not an error, but maybe the user intended to specify a CLI option instead of a file name. */ if (filename[0] == '-') fprintf(stderr, "Warning: Supplied %s file name starts with -\n", type); return 0; }
int main(int argc, char *argv[]) { const struct flashchip *chip = NULL; /* Probe for up to eight flash chips. */ struct flashctx flashes[8] = {{0}}; struct flashctx *fill_flash; const char *name; int namelen, opt, i, j; int startchip = -1, chipcount = 0, option_index = 0, force = 0; #if CONFIG_PRINT_WIKI == 1 int list_supported_wiki = 0; #endif - int read_it = 0, write_it = 0, erase_it = 0, verify_it = 0; + int read_it = 0, write_it = 0, erase_it = 0, verify_it = 0, print_status_reg = 0; + int print_wp_status = 0, wp_list = 0, wp_enable = 0, wp_disable = 0, wp_set_range = 0; int dont_verify_it = 0, list_supported = 0, operation_specified = 0; enum programmer prog = PROGRAMMER_INVALID; int ret = 0;
static const char optstring[] = "r:Rw:v:nVEfc:l:i:p:Lzho:"; static const struct option long_options[] = { {"read", 1, NULL, 'r'}, {"write", 1, NULL, 'w'}, {"erase", 0, NULL, 'E'}, {"verify", 1, NULL, 'v'}, {"noverify", 0, NULL, 'n'}, {"chip", 1, NULL, 'c'}, {"verbose", 0, NULL, 'V'}, {"force", 0, NULL, 'f'}, {"layout", 1, NULL, 'l'}, {"image", 1, NULL, 'i'}, {"list-supported", 0, NULL, 'L'}, {"list-supported-wiki", 0, NULL, 'z'}, {"programmer", 1, NULL, 'p'}, {"help", 0, NULL, 'h'}, {"version", 0, NULL, 'R'}, {"output", 1, NULL, 'o'}, + {"print-status-reg", 0, NULL, LONGOPT_PRINT_STATUSREG}, + {"print-wp-status", 0, NULL, LONGOPT_PRINT_WP_STATUS}, + {"wp-list", 0, NULL, LONGOPT_LIST_BP_RANGES}, + {"wp-enable", optional_argument, NULL, LONGOPT_WP_ENABLE}, + {"wp-disable", 0, NULL, LONGOPT_WP_DISABLE}, + {"wp-set-range", 1, NULL, LONGOPT_SET_BP_RANGE}, {NULL, 0, NULL, 0}, };
char *filename = NULL; char *layoutfile = NULL; #ifndef STANDALONE char *logfile = NULL; #endif /* !STANDALONE */ char *tempstr = NULL; char *pparam = NULL; + char *wp_mode_opt = NULL; + char const *wp_set_range_opt = NULL;
print_version(); print_banner();
if (selfcheck()) exit(1);
setbuf(stdout, NULL); /* FIXME: Delay all operation_specified checks until after command * line parsing to allow --help overriding everything else. */ while ((opt = getopt_long(argc, argv, optstring, long_options, &option_index)) != EOF) { @@ -305,26 +344,81 @@ int main(int argc, char *argv[]) break; case 'o': #ifdef STANDALONE fprintf(stderr, "Log file not supported in standalone mode. Aborting.\n"); cli_classic_abort_usage(); #else /* STANDALONE */ logfile = strdup(optarg); if (logfile[0] == '\0') { fprintf(stderr, "No log filename specified.\n"); cli_classic_abort_usage(); } #endif /* STANDALONE */ break; + /* FIXME(hatim): For the following long options, not _all_ + * of them are mutually exclusive per se (like wp_set_range + * and wp_enable makes sense). There is scope for improvement + * here, but for now let's treat each one as separate operation. */ + case LONGOPT_PRINT_STATUSREG: + if (++operation_specified > 1) { + fprintf(stderr, "More than one operation " + "specified. Aborting.\n"); + cli_classic_abort_usage(); + } + print_status_reg = 1; + break; + case LONGOPT_PRINT_WP_STATUS: + if (++operation_specified > 1) { + fprintf(stderr, "More than one operation " + "specified. Aborting.\n"); + cli_classic_abort_usage(); + } + print_wp_status = 1; + break; + case LONGOPT_LIST_BP_RANGES: + if (++operation_specified > 1) { + fprintf(stderr, "More than one operation " + "specified. Aborting.\n"); + cli_classic_abort_usage(); + } + wp_list = 1; + break; + case LONGOPT_WP_ENABLE: + if (++operation_specified > 1) { + fprintf(stderr, "More than one operation " + "specified. Aborting.\n"); + cli_classic_abort_usage(); + } + wp_enable = 1; + if (optarg) + wp_mode_opt = strdup(optarg); + break; + case LONGOPT_WP_DISABLE: + if (++operation_specified > 1) { + fprintf(stderr, "More than one operation " + "specified. Aborting.\n"); + cli_classic_abort_usage(); + } + wp_disable = 1; + break; + case LONGOPT_SET_BP_RANGE: + if (++operation_specified > 1) { + fprintf(stderr, "More than one operation " + "specified. Aborting.\n"); + cli_classic_abort_usage(); + } + wp_set_range_opt = strdup(optarg); + wp_set_range = 1; + break; default: cli_classic_abort_usage(); break; } }
if (optind < argc) { fprintf(stderr, "Error: Extra parameter found.\n"); cli_classic_abort_usage(); }
if ((read_it | write_it | verify_it) && check_filename(filename, "image")) { cli_classic_abort_usage(); @@ -512,41 +606,141 @@ int main(int argc, char *argv[])
/* Sometimes chip and programmer have more than one bus in common, * and the limit is not exceeded on all buses. Tell the user. */ if ((bitcount(commonbuses) > limitexceeded)) { msg_pdbg("There is at least one interface available which could support the size of\n" "the selected flash chip.\n"); } msg_cerr("This flash chip is too big for this programmer (--verbose/-V gives details).\n" "Use --force/-f to override at your own risk.\n"); ret = 1; goto out_shutdown; }
- if (!(read_it | write_it | verify_it | erase_it)) { + if (!(read_it | write_it | verify_it | erase_it | print_status_reg | + print_wp_status | wp_list | wp_enable | wp_disable | wp_set_range)) { msg_ginfo("No operations were specified.\n"); goto out_shutdown; }
/* Always verify write operations unless -n is used. */ if (write_it && !dont_verify_it) verify_it = 1;
/* Map the selected flash chip again. */ if (map_flash(fill_flash) != 0) { ret = 1; goto out_shutdown; }
+ if (print_status_reg) { + verbose_screen++; + if (fill_flash->chip->status_register) { + for (enum status_register_num SRn = SR1; SRn <= top_status_register(fill_flash); SRn++) + fill_flash->chip->status_register->print(fill_flash, SRn); + fill_flash->chip->status_register->print_wp_mode(fill_flash); + if (fill_flash->chip->wp) + print_range_generic(fill_flash); + } else + cli_statreg_wp_support(fill_flash); + goto out_shutdown; + } + + if (print_wp_status) { + verbose_screen++; + if (fill_flash->chip->status_register || fill_flash->chip->wp) { + msg_ginfo("WP status -\n"); + fill_flash->chip->status_register->print_wp_mode(fill_flash); + if (fill_flash->chip->wp) + print_range_generic(fill_flash); + } else + cli_statreg_wp_support(fill_flash); + goto out_shutdown; + } + + if (wp_list) { + verbose_screen++; + if (fill_flash->chip->wp) { + msg_ginfo("Valid write protection ranges for chip "%s" are -\n", + fill_flash->chip->name); + fill_flash->chip->wp->print_table(fill_flash); + } else + cli_statreg_wp_support(fill_flash); + goto out_shutdown; + } + + if (wp_disable) { + verbose_screen++; + if (fill_flash->chip->wp) { + ret = fill_flash->chip->wp->disable(fill_flash); + } else + cli_statreg_wp_support(fill_flash); + goto out_shutdown; + } + + if (wp_set_range) { + verbose_screen++; + if (fill_flash->chip->wp) { + char *endptr = NULL; + uint8_t start_cmp, end_cmp, has_cmp = pos_bit(fill_flash, CMP) != -1; + if (has_cmp) + start_cmp = get_cmp(fill_flash); + /* FIXME(hatim): Implement error checking */ + uint32_t start = strtoul(extract_param(&wp_set_range_opt, "start", ","), &endptr, 0); + uint32_t len = strtoul(extract_param(&wp_set_range_opt, "len", ","), &endptr, 0); + msg_ginfo("Trying to protect %d kB starting from address 0x%06x...\n", len, start); + ret = fill_flash->chip->wp->set_range(fill_flash, start, len); + if (has_cmp && start_cmp != (end_cmp = get_cmp(fill_flash))) + msg_ginfo("CMP bit was %sset\n", end_cmp ? "" : "un"); + if (ret) + msg_gerr("Failed to protect\n"); + else + msg_ginfo("Protection successful!\n"); + } else + cli_statreg_wp_support(fill_flash); + goto out_shutdown; + } + + if (wp_enable) { + verbose_screen++; + if (fill_flash->chip->status_register) { + enum wp_mode wp_mode = WP_MODE_HARDWARE_UNPROTECTED; + + if (wp_mode_opt) { + if (!strcasecmp(wp_mode_opt, "PERMANENT")) { + wp_mode = WP_MODE_PERMANENT; + } else if (!strcasecmp(wp_mode_opt, "POWER_CYCLE")) { + wp_mode = WP_MODE_POWER_CYCLE; + } else if (!strcasecmp(wp_mode_opt, "SOFTWARE")) { + wp_mode = WP_MODE_SOFTWARE; + } else if (!strcasecmp(wp_mode_opt, "HARDWARE")) { + wp_mode = WP_MODE_HARDWARE_UNPROTECTED; + } else { + msg_gerr("Invalid write protection mode for status register(s)\n"); + cli_classic_abort_usage(); + ret = 1; + goto out_shutdown; + } + } + msg_ginfo("Setting write protection mode to %s ...\n", + wp_mode_opt ? wp_mode_opt : "HARDWARE"); + fill_flash->chip->status_register->set_wp_mode(fill_flash, wp_mode); + ret = !(wp_mode == fill_flash->chip->status_register->get_wp_mode(fill_flash)); + msg_gerr("%s\n", ret ? "Failed" : "Success"); + } else + cli_statreg_wp_support(fill_flash); + goto out_shutdown; + } + /* FIXME: We should issue an unconditional chip reset here. This can be * done once we have a .reset function in struct flashchip. * Give the chip time to settle. */ programmer_delay(100000); ret |= doit(fill_flash, force, filename, read_it, write_it, erase_it, verify_it);
unmap_flash(fill_flash); out_shutdown: programmer_shutdown(); out: for (i = 0; i < chipcount; i++) free(flashes[i].chip); diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl index 34e1fe7..d068ad9 100644 --- a/flashrom.8.tmpl +++ b/flashrom.8.tmpl @@ -30,36 +30,39 @@ .de MTOB . ie (\n[groffhtml]==1) {\ . MTO \$@ . } . el {\ . ie "\$2"" {\ . BR "\$1" "\$3" . } . el {\ . RB "\$2 (la" "\$1" "(ra\$3" . } . } .. -.TH FLASHROM 8 "" "" +.TH FLASHROM 8 "2016-03-13" "0.9.9-unknown" .SH NAME flashrom - detect, read, write, verify and erase flash chips .SH SYNOPSIS .B flashrom \fR[\fB-h\fR|\fB-R\fR|\fB-L\fR|\fB-z\fR|\ \fB-p\fR <programmername>[:<parameters>] - [\fB-E\fR|\fB-r\fR <file>|\fB-w\fR <file>|\fB-v\fR <file>] \ + [\fB-E\fR|\fB-r\fR <file>|\fB-w\fR <file>|\fB-v\fR <file>] \ [\fB-c\fR <chipname>] - [\fB-l\fR <file> [\fB-i\fR <image>]] [\fB-n\fR] [\fB-f\fR]] + [\fB-l\fR <file> [\fB-i\fR <image>]] [\fB-n\fR] [\fB-f\fR]] [\fB-V\fR[\fBV\fR[\fBV\fR]]] [\fB-o\fR <logfile>] + [\fB--print-status-reg\fR] [\fB--print-wp-status\fR] + [\fB--wp-list\fR] [\fB--wp-enable\fR[=<MODE>]] + [\fB--wp-disable\fR] [\fB--wp-set-range\fR start=<start>,len=<len>] .SH DESCRIPTION .B flashrom is a utility for detecting, reading, writing, verifying and erasing flash chips. It's often used to flash BIOS/EFI/coreboot/firmware images in-system using a supported mainboard. However, it also supports various external PCI/USB/parallel-port/serial-port based devices which can program flash chips, including some network cards (NICs), SATA/IDE controller cards, graphics cards, the Bus Pirate device, various FTDI FT2232/FT4232H/FT232H based USB devices, and more. .PP It supports a wide range of DIP32, PLCC32, DIP8, SO8/SOIC8, TSOP32, TSOP40, TSOP48, and BGA chips, which use various protocols such as LPC, FWH, parallel flash, or SPI. .SH OPTIONS @@ -122,26 +125,73 @@ Erase the flash ROM chip. .B "-V, --verbose" More verbose output. This option can be supplied multiple times (max. 3 times, i.e. .BR -VVV ) for even more debug output. .TP .B "-c, --chip" <chipname> Probe only for the specified flash ROM chip. This option takes the chip name as printed by .B "flashrom -L" without the vendor name as parameter. Please note that the chip name is case sensitive. .TP +.B "--print-status-reg" +Print the contents of the status register(s) with bit description along with +write protection mode of status register(s) and protected range. +.TP +.B "--print-wp-status" +Print the write protection mode of status register(s) and protected range. +.TP +.B "--wp-list" +List the valid write protection ranges for the chip. +.TP +.B "--wp-enable[=<MODE>]" +Set write protection mode of status register(s). +.sp +.B "<MODE>" +is optional argument (case insensitive) that takes one of - +.sp +* +.B "HARDWARE" +mode (default) allows writes to status register(s) as long as WP# is high. \ +Writes are disallowed as long as WP# is low. +.sp +* +.B "PERMANENT" +mode will lock the status register(s) and all future attempts to write to it \ +will fail. +.sp +* +.B "POWER_CYCLE" +mode will lock the status register(s) until the next power down-up cycle. \ +After the cycle, SOFTWARE mode will be in effect. +.sp +* +.B "SOFTWARE" +mode allows writes to status register(s) irrespective of level of WP# pin. +.TP +.B "--wp-disable" +Disable any write protection of status register(s) in effect. SOFTWARE mode \ +will be applied after disabling. +.TP +.B "--wp-set-range start=<start>,len=<len>" +Configure status register(s) to protect +.B "<len>" + kB of memory starting from address +.B "<start>". +Both start and len must be supplied. (Consider setting a write protection \ +mode to prevent against configuration changes to status register(s).) +.TP .B "-f, --force" Force one or more of the following actions: .sp * Force chip read and pretend the chip is there. .sp * Force chip access even if the chip is bigger than the maximum supported \ size for the flash bus. .sp * Force erase even if erase is known bad. .sp * Force write even if write is known bad. .TP .B "-l, --layout <file>"
On Wed, 6 Jul 2016 01:57:59 +0530 Hatim Kanchwala hatim@hatimak.me wrote:
Following patches add new infrastructure for status register(s) and locking/unlocking of access protection. Detailed points for explaining each patch are included within them.
Some general thoughts -
- The code has a lot TODOs and FIXMEs (most of which I have addressed
to myself). They are short-term targets and represent the work-in-progress nature of the patches. Future revisions will resolve them.
- There are a lot of comments. Eventually much of that will be
relocated to documentation and wiki, so comments in future revisions will shrink.
I don't see a need to do so. Most of what I have seen so far belongs to the code and not the wiki. Maybe the howto on adding a flash chip should be expanded in the wiki though... when this patch set is completed.
- Some CLI code is borrowed from the ChromiumOS fork
(https://chromium.googlesource.com/chromiumos/third_party/flashrom/). IMO, the exact words to use for the options is subjective.
Let's postpone the CLI discussion altogether. My stance on this in general is that we might not need/want to support all features in the "classic" CLI.
- The names for struct status_register and struct wp defines (in
statusreg_layouts.c and writeprotect_layouts.c respectively) are not very creative and tend to be long. I couldn't come up with a generic scheme that captures most of the information regarding it. You are welcome to offer creative suggestions ;)
Further work -
- Read and write "configuration" registers (a lot of Spansion chips
have those)
- Add tested/untested status for new infrastructure similar to what
we have for chips (like TEST_PREW, etc.)
- write protection modes
- protection ranges
Updating the status bits is already a massive time sink I do not intend to dig even deeper. However, making it more obvious to developers which chips need further work (similar to util/list_yet_unsupported_chips.sh) might be useful. This can even be in the form of -L or -z output, however it should only indicate if an implementation is available and gather this information implicitly from pointers etc. without any dedicated TESTED_* flags. That way maintenance costs are minimal.
- Access protection for non-SPI chips (pointed to by Stefan over IRC)
We definitely want at least a proof of concept that includes support for that before we finish the general API of SRs and WPs.
- Reuse layouts for locking/unlocking (pointed to by Stafan and David
over IRC)
I deem this a CLI aspect.
- Handling WPS bit (a couple of GigaDevice chips have this)
- Exotic chips (mostly Atmel, now Adesto, chips fall under this
category)
Adding support for a AT45DB chip might be useful to see how generic the new API is (or need to be). Their wicked configurations helped me in the past to iron out some completely unrelated bugs. I have not checked if they support any WP scheme though.
Now to the patches themselves... this is obviously no in-depth review. Too many superficial things came up and need to be sorted out/discussed first.
- Don't use char for bytes or integral/countable types like in char pos_bit(...)
- Don't use C99 loop variable declarations because this leads to compile errors with old compilers. The alternative is to use -std=c99 but we are not ready to do that yet.
- static struct range ranges[LEN_RANGES] in writeprotect.c
Doesn't this explode if multiple chip definitions are matched in probes? In any case you should definitely not return pointers to local variables even if they are static. This is very bad API design because it is not obvious to the caller that the data pointed to might be changed outside of its control (cf. strtok and others that used to use an internal static buffer as well but have been superseded by re-entrant variants)
- Initially I was skeptical about the use of pointers to pre-defined structures for the SR and WP definitions. I proposed that myself but only because the alternative shown in the first PoC seemed to be even worse. Looking at the code I felt that it it might disperse the chip definitions too much. I have then played a bit with your code and compared the binary sizes of 0.9.9, your patches and my "fix" on top. I replaced the pointer to the struct status_register in struct flashchip with direct struct instances. These are the results:
0.9.9 text data bss dec hex filename 517381 2268 11712 531361 81ba1 flashrom
hatim: text data bss dec hex filename 554373 11228 12256 577857 8d141 flashrom
stefanct: text data bss dec hex filename 637037 6812 12256 656105 a02e9 flashrom
Your implementation adds about 40kB while the direct instantiation in my try adds almost 120kB. However, this is a maximum because the space for all structs is already pre-allocated and "initialized" in the image whereas for your implementation further structs would need to be added and only space for the pointers are reserved. Additionally, your data section would add to run-time memory usage.
With that in mind I think technically it does not make much sense to separate the definitions. However, this is only one aspect. One other is to provide means to share information. We naturally do this by sharing function pointers and there it has been a huge success because it allows us to easily spot and exploit similarities between chip families. So the separation itself is probably a good thing and I think you went into the right direction with it.
But maybe we can improve on the space requirements of the structs themselves because they are really huge in comparison to the remaining data in struct flashchip. For example: enum status_register_bit layout[MAX_STATUS_REGISTERS + 1][8] would cost us 8*4*sizeof(int) bytes per chip/status register definitoin with most compilers, i.e. 50 kB for 400 SR definitions and 32-bit integers. I tend to ignore this mostly because that's not that much for ordinary systems but e.g. WLAN routers are still stuck with a few MB of flash and there these 50 kB could be spent better. However, since we share these definitions in your implementation a lot it should be way less than these 50 kB... but if you can think of an approach that uses less memory without making the code much more complicated I am all for it. Maybe we could use some feature_bits to indicate the number of SRs of the respective chip and can get rid of the +1 at least?
- The function prototypes you added to chipdrivers.h should be moved to the respective header files.
- Some of the _generic functions in writeprotect are not really that generic. They almost only deal with SPI-specific behavior and should neither be named so generically and possibly not even reside in that module but spi25_statusreg.c instead. Please try to better abstract the generic WP API (as used by the function pointers) from the SPI-specific implementations. BTW we might get away without any print and print_wp_mode function pointers because they should simply print the generic representation, no? So spi_prettyprint_status_register_generic for example might actually be an actual generic function to be moved to a more general file (it currently resides in spi25_statusreg.c).
- Please comment: static char const *bit_string[3][32] uint8_t convention = 1, multiple = 0
- Please exploit and refine our dummy programmer to test the status register and OTP implementations of at least some variations. Especially simulating the latter makes sense in regression testing for obvious reasons :)
- Many of above generic statements apply to OTP as well, e.g. the print_status pointer might be superfluous.
- What about OTP in non-SPI chips? Have you researched that yet? I can't remember seeing that anywhere but in new chip generations I would not be surprised if there is support for it.
Don't despair due to the amount of criticism you might perceive. In general I think you are doing a very good job! I am fully aware that the goal of implementing all of this in a generic way is not easy but it is necessary to make it maintainable in the future. Thanks for your work!
Hope that helps a bit and please let me know if anything is unclear. I also might have not grasp everything and might be wrong on some points. Don't hesitate to correct me in that case :)