- 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__ */