[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