[flashrom] [PATCH 2/4] OTP/Security Register infrastructure
Hatim Kanchwala
hatim at hatimak.me
Thu Jul 28 14:15:27 CEST 2016
- Read, write or erase OTP memory
- Two distinct models (not exhaustive) -
- Security sector (Eon) - Separate memory array accessible as a normal sector while in OTP mode, WRSR locks it permanently
- Security Registers (GigaDevice and Winbond) - Separate memory location with dedicated opcodes for read, program and erase; OTP status controlled by Lock Bits (LB1, LB2, ...) in status register(s)
- struct region represents a quantum of OTP memory - start address, size and modifier bit in status register (generic design applies to all aforementioned models)
- struct flashchip contains pointer to struct otp, which has members to represent OTP regions and function pointers to fetch and print status, read, write, erase and lock OTP regions
Signed-off-by: Hatim Kanchwala <hatim at hatimak.me>
---
Makefile | 2 +-
chipdrivers.h | 23 ++++
flash.h | 23 ++++
otp.c | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
otp.h | 28 +++++
spi.h | 20 +++
spi25.c | 73 +++++++++++
7 files changed, 549 insertions(+), 1 deletion(-)
create mode 100644 otp.c
create mode 100644 otp.h
diff --git a/Makefile b/Makefile
index c274e79..e97f641 100644
--- a/Makefile
+++ b/Makefile
@@ -503,27 +503,27 @@ 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 \
+ spi25.o spi25_statusreg.o otp.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.
diff --git a/chipdrivers.h b/chipdrivers.h
index d3b27cc..ab18cc5 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 "otp.h" /* For enum otp_region */
#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);
@@ -52,26 +53,30 @@ int spi_block_erase_62(struct flashctx *flash, unsigned int addr, unsigned int b
int spi_block_erase_81(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
int spi_block_erase_c4(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
int spi_block_erase_c7(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
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);
+int spi_enter_otp_mode(struct flashctx *flash);
+int spi_sec_reg_read(struct flashctx *flash, uint8_t *buf, uint32_t start_addr, uint32_t len);
+int spi_sec_reg_prog(struct flashctx *flash, uint8_t const *buf, uint32_t start_addr, uint32_t len);
+int spi_sec_reg_erase(struct flashctx *flash, uint32_t addr);
/* 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);
@@ -104,26 +109,44 @@ 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);
+/* otp.c */
+int eon_status_generic(struct flashctx *flash, enum otp_region otp_region);
+int eon_print_status_generic(struct flashctx *flash);
+int eon_read_generic(struct flashctx *flash, uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len);
+int eon_write_generic(struct flashctx *flash, const uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len);
+int eon_erase_generic(struct flashctx *flash, enum otp_region otp_region);
+int eon_lock_generic(struct flashctx *flash, enum otp_region otp_region);
+int gd_w_status_generic(struct flashctx *flash, enum otp_region otp_region);
+int gd_w_print_status_generic(struct flashctx *flash);
+int gd_w_read_generic(struct flashctx *flash, uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len);
+int gd_w_write_generic(struct flashctx *flash, const uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len);
+int gd_erase_generic(struct flashctx *flash, enum otp_region otp_region);
+int gd_w_lock_generic(struct flashctx *flash, enum otp_region otp_region);
+
/* 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);
diff --git a/flash.h b/flash.h
index 631c1a8..c37426c 100644
--- a/flash.h
+++ b/flash.h
@@ -27,26 +27,27 @@
#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 "otp.h"
#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))
@@ -240,26 +241,48 @@ struct flashchip {
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 otp {
+ struct region {
+ /* This address corresponds to the first byte in the OTP memory region. */
+ uint32_t addr;
+ uint32_t size; /* in bytes */
+
+ /* Usually, setting this modifier bit will permanently lock the
+ * corresponding OTP region against writes.
+ * Not all chips have a modifier bit (AMIC, Macronix). */
+ enum status_register_bit status_bit;
+ } region[MAX_OTP_REGIONS + 1]; /* We need one more than MAX_STATUS_REGISTERS */
+
+ int (*status) (struct flashctx *flash, enum otp_region otp_region);
+ int (*print_status) (struct flashctx *flash);
+ int (*read) (struct flashctx *flash, uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_addr, uint32_t len);
+ int (*write) (struct flashctx *flash, const uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_addr, uint32_t len);
+ int (*erase) (struct flashctx *flash, enum otp_region otp_region);
+ int (*lock) (struct flashctx *flash, enum otp_region otp_region);
+ } *otp;
};
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/otp.c b/otp.c
new file mode 100644
index 0000000..4af4b84
--- /dev/null
+++ b/otp.c
@@ -0,0 +1,381 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2016 Hatim Kanchwala <hatim at 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"
+
+/* Return the top-most (highest) OTP region of flash. */
+static enum otp_region top_otp_region(struct flashctx *flash) {
+ enum otp_region otp_region = OTP_REG_1;
+ struct region *region = flash->chip->otp->region;
+
+ while (region[otp_region++].size != 0)
+ ;
+ return otp_region - 2;
+}
+
+/* Some standard error checking used by program and erase functions. */
+static int otp_error_check(struct flashctx *flash, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len) {
+ if (otp_region > top_otp_region(flash)) {
+ msg_cdbg("Trying to access non-existent OTP region %d\n%s has only %d OTP regions\n",
+ otp_region + 1, flash->chip->name, top_otp_region(flash) + 1);
+ return 1;
+ }
+ if (start_byte + len > flash->chip->otp->region[otp_region].size) {
+ msg_cdbg("OTP region for %s is %d bytes\n", flash->chip->name,
+ flash->chip->otp->region[otp_region].size);
+ return 1;
+ }
+ return 0;
+}
+
+/* === Eon chip specific functions === */
+static uint8_t bp_bitfield, to_restore = 0;
+
+static void save_bp(struct flashctx *flash) {
+ uint8_t status = flash->chip->status_register->read(flash, SR1);
+ uint32_t bp_bitmask = flash->chip->wp->bp_bitmask(flash);
+ bp_bitfield = (status & bp_bitmask) >> pos_bit(flash, BP0);
+}
+
+static int restore_bp(struct flashctx *flash) {
+ uint8_t status = flash->chip->status_register->read(flash, SR1);
+ uint32_t bp_bitmask = flash->chip->wp->bp_bitmask(flash);
+ status = ((status & ~bp_bitmask) | (bp_bitfield << pos_bit(flash, BP0))) & 0xff;
+ return flash->chip->status_register->write(flash, SR1, status);
+}
+
+/* Enter OTP mode. If any Block Protect bits are set, then save
+ * their state and temporarily unset them all. */
+static int enter_otp_mode(struct flashctx *flash) {
+ uint8_t bp = flash->chip->status_register->read(flash, SR1) & flash->chip->wp->bp_bitmask(flash);
+ if (bp) {
+ msg_cdbg("Need to unset all BP bits before entering OTP mode ...\n");
+ msg_cdbg("BP bits will be restored to 0x%02x\n", bp >> pos_bit(flash, BP0));
+ to_restore = 1;
+ save_bp(flash);
+ }
+ return spi_enter_otp_mode(flash);
+}
+
+/* Exit OTP mode. If any Block Protect bits were set prior to issuing
+ * an Enter OTP, then restore those bits after exiting. */
+static int exit_otp_mode(struct flashctx *flash) {
+ int result = spi_write_disable(flash);
+ if (result) {
+ msg_cdbg("Couldn't exit OTP mode\n");
+ return result;
+ }
+
+ if (to_restore) {
+ msg_cdbg("Restoring BP bits to their state prior to entering OTP mode ...\n");
+ result = restore_bp(flash);
+ if (result)
+ msg_cdbg("Couldn't restore BP bits\n");
+ }
+ to_restore = 0;
+ return result;
+}
+
+int eon_status_generic(struct flashctx *flash, enum otp_region otp_region) {
+ enter_otp_mode(flash);
+ uint8_t status = (flash->chip->status_register->read(flash, SR1) &
+ (1 << pos_bit(flash, SRP0))) ? 1 : 0;
+ exit_otp_mode(flash);
+ return status;
+}
+
+int eon_print_status_generic(struct flashctx *flash) {
+ enum otp_region top_region = top_otp_region(flash), region_n;
+ msg_cdbg("%s contains %d OTP memory region%s (also called OTP sector%s) -\n",
+ flash->chip->name, top_region + 1,
+ (top_region == 0) ? "" : "s", (top_region == 0) ? "" : "s");
+
+ for (region_n = OTP_REG_1; region_n <= top_region; region_n++) {
+ msg_cdbg("OTP memory region %d: %d bytes, controlled by %s bit in status register %d "
+ "(while in OTP mode)\n", region_n + 1, flash->chip->otp->region[region_n].size,
+ statreg_bit_desc[flash->chip->otp->region[region_n].status_bit][0],
+ (pos_bit(flash, flash->chip->otp->region[region_n].status_bit) / 8) + 1);
+ if (flash->chip->otp->status(flash, region_n)) {
+ msg_cdbg("OTP memory region %d is permanently locked and cannot be erased "
+ "or written to\n", region_n + 1);
+ }
+ }
+ return 0;
+}
+
+/* Read len bytes of the security register (corresponding to otp_region) into buf,
+ * starting from start_byte. */
+int eon_read_generic(struct flashctx *flash, uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len) {
+ int result = otp_error_check(flash, otp_region, start_byte, len);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+
+ enter_otp_mode(flash);
+ result = flash->chip->read(flash, buf, start_byte, len);
+ exit_otp_mode(flash);
+
+ if (result)
+ msg_cerr("%s failed\n", __func__);
+ return result;
+}
+
+/* Write len bytes to the security register (corresponding to otp_region) form buf,
+ * starting from start_byte. */
+int eon_write_generic(struct flashctx *flash, const uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len) {
+ int result = otp_error_check(flash, otp_region, start_byte, len);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+ if (flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("OTP memory region %d is permanently locked and cannot be written to\n",
+ otp_region + 1);
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ }
+
+ enter_otp_mode(flash);
+ result = flash->chip->write(flash, buf, start_byte, len);
+ exit_otp_mode(flash);
+
+ if (result)
+ msg_cerr("%s failed\n", __func__);
+ return result;
+}
+
+/* Erase the security register corresponding to otp_region. */
+int eon_erase_generic(struct flashctx *flash, enum otp_region otp_region) {
+ int result = otp_error_check(flash, otp_region, 0x000000, 0);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+ if (flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("OTP memory region %d is permanently locked and cannot be written to\n",
+ otp_region + 1);
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ }
+
+ enter_otp_mode(flash);
+ result = spi_block_erase_20(flash, flash->chip->otp->region[otp_region].addr,
+ flash->chip->otp->region[otp_region].size);
+ exit_otp_mode(flash);
+
+ if (result)
+ msg_cerr("%s failed\n", __func__);
+ return result;
+}
+
+/* Lock the OTP memory corresponding to otp_region. The corresponding bit
+ * in the status register is set (which is one-time programmable). For Eon
+ * chips, the SRP/SRP0/SRWD bit is served as OTP it while in OTP mode.
+ * Note that if the bit was already set, the function does not consider
+ * it a point of failure. */
+int eon_lock_generic(struct flashctx *flash, enum otp_region otp_region) {
+ int result = otp_error_check(flash, otp_region, 0x000000, 0);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+ enum status_register_bit status_bit = flash->chip->otp->region[otp_region].status_bit;
+ if (pos_bit(flash, status_bit) == -1) {
+ /* Check if such a bit even exists in the status register in the first place. */
+ // TODO(hatim): This block does not seem to have many use cases as the error
+ // can be avoided while reviewing patches itself
+ msg_cdbg("OTP modifier bit %s for %s defined incorrectly\n",
+ statreg_bit_desc[status_bit][0], flash->chip->name);
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ }
+ if (flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("OTP modifier bit already set, "
+ "cannot alter value as it is one-time-programmable only\n");
+ // FIXME(hatim): Should we return zero or non-zero here?
+ return 0;
+ }
+
+ enter_otp_mode(flash);
+ /* WRSR will set OTP modifier bit irrespective of status byte supplied. */
+ flash->chip->status_register->write(flash, SR1, 1 << pos_bit(flash, SRP0));
+ exit_otp_mode(flash);
+
+ if (!flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("Unable to set OTP modifier bit\n");
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ } else
+ return 0;
+}
+
+
+/* === GigaDevice and (most) Winbond chip specific functions === */
+/* Get the OTP modifier bit (these are usually the LB1, LB2, ... bits) from the status
+ * registers. */
+static int gd_w_get_otp_bit(struct flashctx *flash, enum status_register_bit modifier_bit) {
+ enum status_register_num SRn = pos_bit(flash, modifier_bit) / 8;
+ uint8_t modifier_bit_mask = 1 << (pos_bit(flash, modifier_bit) - (SRn * 8));
+
+ uint8_t status = flash->chip->status_register->read(flash, SRn);
+ return status & modifier_bit_mask;
+}
+
+/* Set the OTP modifier bit (these are usually the LB1, LB2, ... bits) in the status registers.
+ * We take no value of the bit as an argument because they are one-time-programmable only and
+ * they can only be set. */
+static int gd_w_set_otp_bit(struct flashctx *flash, enum status_register_bit modifier_bit) {
+ enum status_register_num SRn = pos_bit(flash, modifier_bit) / 8;
+ uint8_t modifier_bit_mask = 1 << (pos_bit(flash, modifier_bit) - (SRn * 8));
+
+ uint8_t status = flash->chip->status_register->read(flash, SRn);
+ status = (status & ~modifier_bit_mask) & 0xff;
+ status |= modifier_bit_mask;
+ return flash->chip->status_register->write(flash, SRn, status);
+}
+
+int gd_w_status_generic(struct flashctx *flash, enum otp_region otp_region) {
+ return gd_w_get_otp_bit(flash, flash->chip->otp->region[otp_region].status_bit) ? 1 : 0;
+}
+
+int gd_w_print_status_generic(struct flashctx *flash) {
+ enum otp_region top_region = top_otp_region(flash), region_n;
+ msg_cdbg("%s contains %d OTP memory region%s (also called Security Register%s) -\n",
+ flash->chip->name, top_region + 1,
+ (top_region == 0) ? "" : "s", (top_region == 0) ? "" : "s");
+
+ for (region_n = OTP_REG_1; region_n <= top_region; region_n++) {
+ msg_cdbg("OTP memory region %d: %d bytes, controlled by %s bit in status register %d\n",
+ region_n + 1,
+ flash->chip->otp->region[region_n].size,
+ statreg_bit_desc[flash->chip->otp->region[region_n].status_bit][0],
+ (pos_bit(flash, flash->chip->otp->region[region_n].status_bit) / 8) + 1);
+ if (flash->chip->otp->status(flash, region_n)) {
+ msg_cdbg("OTP memory region %d is permanently locked and cannot be erased "
+ "or written to\n", region_n + 1);
+ }
+ }
+ return 0;
+}
+
+/* Read len bytes of the security register (corresponding to otp_region) into buf,
+ * starting from start_byte. */
+int gd_w_read_generic(struct flashctx *flash, uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len) {
+ int result = otp_error_check(flash, otp_region, start_byte, len);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+
+ /* Prefix the first couple of pre-defined bits of the security register address. */
+ result = spi_sec_reg_read(flash, buf, flash->chip->otp->region[otp_region].addr | start_byte, len);
+ if (result)
+ msg_cerr("%s failed\n", __func__);
+ return result;
+}
+
+/* Write len bytes to the security register (corresponding to otp_region) form buf,
+ * starting from start_byte. */
+int gd_w_write_generic(struct flashctx *flash, const uint8_t *buf, enum otp_region otp_region,
+ uint32_t start_byte, uint32_t len) {
+ int result = otp_error_check(flash, otp_region, start_byte, len);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+ if (flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("OTP memory region %d is permanently locked and cannot be written to\n",
+ otp_region + 1);
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ }
+
+ /* Prefix the first couple of pre-defined bits of the security register address. */
+ result = spi_sec_reg_prog(flash, buf, flash->chip->otp->region[otp_region].addr | start_byte, len);
+ if (result)
+ msg_cerr("%s failed\n", __func__);
+ return result;
+}
+
+/* Erase the security register corresponding to otp_region. */
+int gd_erase_generic(struct flashctx *flash, enum otp_region otp_region) {
+ int result = otp_error_check(flash, otp_region, 0x000000, 0);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+ if (flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("OTP memory region %d is permanently locked and cannot be erased\n",
+ otp_region + 1);
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ }
+
+ result = spi_sec_reg_erase(flash, flash->chip->otp->region[otp_region].addr);
+ if (result)
+ msg_cerr("%s failed\n", __func__);
+ return result;
+}
+
+/* Lock the OTP memory corresponding to otp_region. The corresponding bit
+ * in the status register is set (which is one-time programmable).
+ * Note that if the bit was already set, the function does not consider
+ * it a point of failure. */
+int gd_w_lock_generic(struct flashctx *flash, enum otp_region otp_region) {
+ int result = otp_error_check(flash, otp_region, 0x000000, 0);
+ if (result) {
+ msg_cerr("%s failed\n", __func__);
+ return result;
+ }
+
+ enum status_register_bit status_bit = flash->chip->otp->region[otp_region].status_bit;
+ if (pos_bit(flash, status_bit) == -1) {
+ /* Check if such a bit even exists in the status register in the first place. */
+ // TODO(hatim): This block does not seem to have many use cases as the error
+ // can be avoided while reviewing patches itself
+ msg_cdbg("OTP modifier bit %s for %s defined incorrectly\n",
+ statreg_bit_desc[status_bit][0], flash->chip->name);
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ }
+ if (flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("OTP modifier bit already set, "
+ "cannot alter value as it is one-time-programmable only\n");
+ // FIXME(hatim): Should we return zero or non-zero here?
+ return 0;
+ } else {
+ result = gd_w_set_otp_bit(flash, status_bit);
+ if (result)
+ msg_cerr("%s failed\n", __func__);
+ if (!flash->chip->otp->status(flash, otp_region)) {
+ msg_cdbg("Unable to set OTP modifier bit\n");
+ msg_cerr("%s failed\n", __func__);
+ return 1;
+ }
+ return result;
+ }
+}
diff --git a/otp.h b/otp.h
new file mode 100644
index 0000000..790c8c5
--- /dev/null
+++ b/otp.h
@@ -0,0 +1,28 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2016 Hatim Kanchwala <hatim at 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 __OTP_H__
+#define __OTP_H__ 1
+
+#define MAX_OTP_REGIONS 4
+
+enum otp_region { OTP_REG_1 = 0, OTP_REG_2, OTP_REG_3 };
+
+#endif /* !__OTP_H__ */
diff --git a/spi.h b/spi.h
index f061041..90bff5f 100644
--- a/spi.h
+++ b/spi.h
@@ -148,22 +148,42 @@
/* 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
#define JEDEC_AAI_WORD_PROGRAM_OUTSIZE 0x06
#define JEDEC_AAI_WORD_PROGRAM_CONT_OUTSIZE 0x03
#define JEDEC_AAI_WORD_PROGRAM_INSIZE 0x00
+/* Enter OTP mode (supported by most Eon chips) */
+#define JEDEC_ENTER_OTP 0x3A
+#define JEDEC_ENTER_OTP_OUTSIZE 0x01
+#define JEDEC_ENTER_OTP_INSIZE 0x00
+
+/* Read Security Register(s) (supported by most GigaDevice chips) */
+#define JEDEC_READ_SEC_REG 0x48
+#define JEDEC_READ_SEC_REG_OUTSIZE 0x05
+/* JEDEC_READ_SEC_REG_INSIZE any length */
+
+/* Program Security Register(s) (supported by most GigaDevice chips) */
+#define JEDEC_PROG_BYTE_SEC_REG 0x42
+#define JEDEC_PROG_BYTE_SEC_REG_OUTSIZE 0x05
+#define JEDEC_PROG_BYTE_SEC_REG_INSIZE 0x00
+
+/* Erase Security Register(s) (supported by most GigaDevice chips) */
+#define JEDEC_ERASE_SEC_REG 0x44
+#define JEDEC_ERASE_SEC_REG_OUTSIZE 0x04
+#define JEDEC_ERASE_SEC_REG_INSIZE 0x00
+
/* Error codes */
#define SPI_GENERIC_ERROR -1
#define SPI_INVALID_OPCODE -2
#define SPI_INVALID_ADDRESS -3
#define SPI_INVALID_LENGTH -4
#define SPI_FLASHROM_BUG -5
#define SPI_PROGRAMMER_ERROR -6
#endif /* !__SPI_H__ */
diff --git a/spi25.c b/spi25.c
index 51db4c8..a38623c 100644
--- a/spi25.c
+++ b/spi25.c
@@ -1259,13 +1259,86 @@ int default_spi_write_aai(struct flashctx *flash, const uint8_t *buf, unsigned i
if (spi_chip_write_1(flash, buf + pos - start, pos, pos % 2))
return SPI_GENERIC_ERROR;
pos += pos % 2;
}
return 0;
bailout:
result = spi_write_disable(flash);
if (result != 0)
msg_cerr("%s failed to disable AAI mode.\n", __func__);
return SPI_GENERIC_ERROR;
}
+
+
+/* This function is specific to mostly Eon chips. It maps the
+ * additional OTP sector to the top or bottom sector (depending
+ * on the chip). The mapped sector behaves like just another
+ * normal sector. */
+int spi_enter_otp_mode(struct flashctx *flash)
+{
+ static const unsigned char cmd[JEDEC_ENTER_OTP_OUTSIZE] = { JEDEC_ENTER_OTP };
+ return spi_send_command(flash, sizeof(cmd), 0, cmd, NULL);
+}
+
+int spi_sec_reg_read(struct flashctx *flash, uint8_t *buf, uint32_t start_addr, uint32_t len)
+{
+ /* We assume that start_addr and len are correct and proceed without any error checking. */
+ uint8_t cmd[JEDEC_READ_SEC_REG_OUTSIZE] = {
+ (uint8_t)JEDEC_READ_SEC_REG,
+ (uint8_t)(start_addr >> 16) & 0xff,
+ (uint8_t)(start_addr >> 8) & 0xff,
+ (uint8_t)start_addr & 0xff,
+ (uint8_t)0x00,
+ };
+
+ int result = spi_send_command(flash, sizeof(cmd), len, cmd, buf);
+ if (result)
+ msg_cerr("%s failed.\n", __func__);
+ return result;
+}
+
+int spi_sec_reg_prog(struct flashctx *flash, uint8_t const *buf, uint32_t start_addr, uint32_t len)
+{
+ /* We assume that start_addr and len are correct, the security register is unlocked
+ * and proceed without any error checking. */
+ int ret = spi_write_enable(flash);
+ if (ret) {
+ msg_cerr("%s failed\n", __func__);
+ return ret;
+ }
+
+ uint8_t cmd[JEDEC_PROG_BYTE_SEC_REG_OUTSIZE - 1 + len];
+ cmd[0] = (uint8_t)JEDEC_PROG_BYTE_SEC_REG;
+ cmd[1] = (uint8_t)(start_addr >> 16) & 0xff;
+ cmd[2] = (uint8_t)(start_addr >> 8) & 0xff;
+ cmd[3] = (uint8_t)start_addr & 0xff;
+ memcpy((void *)&cmd[4], (void *)buf, (size_t)len);
+ // FIXME(hatim): We should probably poll WIP bit
+ ret = spi_send_command(flash, sizeof(cmd), 0, cmd, NULL);
+ if (ret)
+ msg_cerr("%s\n", __func__);
+ return ret;
+}
+
+int spi_sec_reg_erase(struct flashctx *flash, uint32_t addr)
+{
+ /* We assume that addr is correct and proceed without any error checking. */
+ int ret = spi_write_enable(flash);
+ if (ret) {
+ msg_cerr("%s failed\n", __func__);
+ return ret;
+ }
+
+ uint8_t cmd[JEDEC_ERASE_SEC_REG_OUTSIZE] = {
+ (uint8_t)JEDEC_ERASE_SEC_REG,
+ (uint8_t)(addr >> 16) & 0xff,
+ (uint8_t)(addr >> 8) & 0xff,
+ (uint8_t)addr & 0xff,
+ };
+ // FIXME(hatim): We should probably poll WIP bit
+ ret = spi_send_command(flash, sizeof(cmd), 0, cmd, NULL);
+ if (ret)
+ msg_cerr("%s\n", __func__);
+ return ret;
+}
--
2.7.4
More information about the flashrom
mailing list