[flashrom] [PATCH 2/2] ichspi: add support for Intel Hardware Sequencing

Stefan Tauner stefan.tauner at student.tuwien.ac.at
Wed Oct 26 15:35:18 CEST 2011


Based on the new opaque programmer framework this patch adds support
for Intel Hardware Sequencing on ICH8 and its successors.

By default (or when setting the ich_spi_mode option to auto)
the module tries to use swseq and only activates hwseq if need be:
- if important opcodes are inaccessible due to lockdown
- if more than one flash chip is attached.
The other options (swseq, hwseq) select the respective mode (if possible).

A general description of Hardware Sequencing can be found in this blog entry:
http://blogs.coreboot.org/blog/2011/06/11/gsoc-2011-flashrom-part-1/

TODO: adding real documentation when we have a directory for it

Signed-off-by: Stefan Tauner <stefan.tauner at student.tuwien.ac.at>
---
 flashrom.8 |   20 +++++
 ichspi.c   |  268 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 283 insertions(+), 5 deletions(-)

diff --git a/flashrom.8 b/flashrom.8
index a8f4660..66cde4f 100644
--- a/flashrom.8
+++ b/flashrom.8
@@ -303,6 +303,26 @@ is the I/O port number (must be a multiple of 8). In the unlikely case
 flashrom doesn't detect an active IT87 LPC<->SPI bridge, please send a bug
 report so we can diagnose the problem.
 .sp
+If you have an Intel chipset with an ICH8 or later southbridge with SPI flash
+attached, and if a valid descriptor was written to it (e.g. by the vendor), the
+chipset provides an alternative way to access the flash chip(s) named
+.BR "Hardware Sequencing" .
+It is much simpler than the normal access method (called
+.BR "Software Sequencing" "),"
+but does not allow the software to choose the SPI commands to be sent.
+You can use the
+.sp
+.B "  flashrom \-p internal:ich_spi_mode=value"
+.sp
+syntax where value can be
+.BR auto ", " swseq " or " hwseq .
+By default
+.RB "(or when setting " ich_spi_mode=auto )
+the module tries to use swseq and only activates hwseq if need be (e.g. if
+important opcodes are inaccessible due to lockdown; or if more than one flash
+chip is attached). The other options (swseq, hwseq) select the respective mode
+(if possible).
+.sp
 If you have an Intel chipset with an ICH6 or later southbridge and if you want
 to set specific IDSEL values for a non-default flash chip or an embedded
 controller (EC), you can use the
diff --git a/ichspi.c b/ichspi.c
index bc03554..1d5dd34 100644
--- a/ichspi.c
+++ b/ichspi.c
@@ -575,6 +575,25 @@ static int program_opcodes(int ich_generation, OPCODES *op, int enable_undo)
 	return 0;
 }
 
+/* Returns true if the most important opcodes are accessible. */
+static int ich_check_opcodes()
+{
+	uint8_t ops[] = {
+		JEDEC_READ,
+		JEDEC_BYTE_PROGRAM,
+		JEDEC_RDSR,
+		0
+	};
+	int i = 0;
+	while (ops[i] != 0) {
+		msg_pspew("checking for opcode 0x%02x\n", ops[i]);
+		if (find_opcode(curopcodes, ops[i]) == -1)
+			return 0;
+		i++;
+	}
+	return 1;
+}
+
 /*
  * Try to set BBAR (BIOS Base Address Register), but read back the value in case
  * it didn't stick.
@@ -1091,7 +1110,11 @@ static int ich_spi_send_command(unsigned int writecnt, unsigned int readcnt,
 	return result;
 }
 
-#if 0
+static struct hwseq_data {
+	uint32_t size_comp0;
+	uint32_t size_comp1;
+} hwseq_data;
+
 /* Sets FLA in FADDR to (addr & 0x01FFFFFF) without touching other bits. */
 static void ich_hwseq_set_addr(uint32_t addr)
 {
@@ -1160,7 +1183,184 @@ static int ich_hwseq_wait_for_cycle_complete(unsigned int timeout,
 	}
 	return 0;
 }
-#endif
+
+int ich_hwseq_probe(struct flashchip *flash)
+{
+	uint32_t total_size, boundary;
+	uint32_t erase_size_low, size_low, erase_size_high, size_high;
+	struct block_eraser *eraser;
+
+	total_size = hwseq_data.size_comp0 + hwseq_data.size_comp1;
+	msg_cdbg("Found %d attached SPI flash chip",
+		 (hwseq_data.size_comp1 != 0) ? 2 : 1);
+	if (hwseq_data.size_comp1 != 0)
+		msg_cdbg("s with a combined");
+	else
+		msg_cdbg(" with a");
+	msg_cdbg(" density of %d kB.\n", total_size / 1024);
+	flash->total_size = total_size / 1024;
+
+	eraser = &(flash->block_erasers[0]);
+	boundary = (REGREAD32(ICH9_REG_FPB) & FPB_FPBA) << 12;
+	size_high = total_size - boundary;
+	erase_size_high = ich_hwseq_get_erase_block_size(boundary);
+
+	if (boundary == 0) {
+		msg_cdbg("There is only one partition containing the whole "
+			 "address space (0x%06x - 0x%06x).\n", 0, size_high-1);
+		eraser->eraseblocks[0].size = erase_size_high;
+		eraser->eraseblocks[0].count = size_high / erase_size_high;
+		msg_cdbg("There are %d erase blocks with %d B each.\n",
+			 size_high / erase_size_high, erase_size_high);
+	} else {
+		msg_cdbg("The flash address space (0x%06x - 0x%06x) is divided "
+			 "at address 0x%06x in two partitions.\n",
+			 0, size_high-1, boundary);
+		size_low = total_size - size_high;
+		erase_size_low = ich_hwseq_get_erase_block_size(0);
+
+		eraser->eraseblocks[0].size = erase_size_low;
+		eraser->eraseblocks[0].count = size_low / erase_size_low;
+		msg_cdbg("The first partition ranges from 0x%06x to 0x%06x.\n",
+			 0, size_low-1);
+		msg_cdbg("In that range are %d erase blocks with %d B each.\n",
+			 size_low / erase_size_low, erase_size_low);
+
+		eraser->eraseblocks[1].size = erase_size_high;
+		eraser->eraseblocks[1].count = size_high / erase_size_high;
+		msg_cdbg("The second partition ranges from 0x%06x to 0x%06x.\n",
+			 boundary, size_high-1);
+		msg_cdbg("In that range are %d erase blocks with %d B each.\n",
+			 size_high / erase_size_high, erase_size_high);
+	}
+	flash->tested = TEST_OK_PREW;
+	return 1;
+}
+
+int ich_hwseq_block_erase(struct flashchip *flash,
+			  unsigned int addr,
+			  unsigned int len)
+{
+	uint32_t erase_block;
+	uint16_t hsfc;
+	uint32_t timeout = 5000 * 1000; /* 5 s for max 64 kB */
+
+	erase_block = ich_hwseq_get_erase_block_size(addr);
+	if (len != erase_block) {
+		msg_cerr("Erase block size for address 0x%06x is %d B, "
+			 "but requested erase block size is %d B. "
+			 "Not erasing anything.\n", addr, erase_block, len);
+		return -1;
+	}
+
+	/* Although the hardware supports this (it would erase the whole block
+	 * containing the address) we play safe here. */
+	if (addr % erase_block != 0) {
+		msg_cerr("Erase address 0x%06x is not aligned to the erase "
+			 "block boundary (any multiple of %d). "
+			 "Not erasing anything.\n", addr, erase_block);
+		return -1;
+	}
+
+	if (addr + len > flash->total_size * 1024) {
+		msg_perr("Request to erase some inaccessible memory address(es)"
+			 " (addr=0x%x, len=%d). "
+			 "Not erasing anything.\n", addr, len);
+		return -1;
+	}
+
+	msg_pdbg("Erasing %d bytes starting at 0x%06x.\n", len, addr);
+
+	/* make sure FDONE, FCERR, AEL are cleared by writing 1 to them */
+	REGWRITE16(ICH9_REG_HSFS, REGREAD16(ICH9_REG_HSFS));
+
+	hsfc = REGREAD16(ICH9_REG_HSFC);
+	hsfc &= ~HSFC_FCYCLE; /* clear operation */
+	hsfc |= (0x3 << HSFC_FCYCLE_OFF); /* set erase operation */
+	hsfc |= HSFC_FGO; /* start */
+	msg_pdbg("HSFC used for block erasing: ");
+	prettyprint_ich9_reg_hsfc(hsfc);
+	REGWRITE16(ICH9_REG_HSFC, hsfc);
+
+	if (ich_hwseq_wait_for_cycle_complete(timeout, len))
+		return -1;
+	return 0;
+}
+
+int ich_hwseq_read(struct flashchip *flash, uint8_t *buf, int addr, int len)
+{
+	uint16_t hsfc;
+	uint16_t timeout = 100 * 60;
+	uint8_t block_len;
+
+	if (addr < 0 || addr + len > flash->total_size * 1024) {
+		msg_perr("Request to read from an inaccessible memory address "
+			 "(addr=0x%x, len=%d).\n", addr, len);
+		return -1;
+	}
+
+	msg_pdbg("Reading %d bytes starting at 0x%06x.\n", len, addr);
+	/* clear FDONE, FCERR, AEL by writing 1 to them (if they are set) */
+	REGWRITE16(ICH9_REG_HSFS, REGREAD16(ICH9_REG_HSFS));
+
+	while (len > 0) {
+		block_len = min(len, opaque_programmer->max_data_read);
+		ich_hwseq_set_addr(addr);
+		hsfc = REGREAD16(ICH9_REG_HSFC);
+		hsfc &= ~HSFC_FCYCLE; /* set read operation */
+		hsfc &= ~HSFC_FDBC; /* clear byte count */
+		/* set byte count */
+		hsfc |= (((block_len - 1) << HSFC_FDBC_OFF) & HSFC_FDBC);
+		hsfc |= HSFC_FGO; /* start */
+		REGWRITE16(ICH9_REG_HSFC, hsfc);
+
+		if (ich_hwseq_wait_for_cycle_complete(timeout, block_len))
+			return 1;
+		ich_read_data(buf, block_len, ICH9_REG_FDATA0);
+		addr += block_len;
+		buf += block_len;
+		len -= block_len;
+	}
+	return 0;
+}
+
+int ich_hwseq_write(struct flashchip *flash, uint8_t *buf, int addr, int len)
+{
+	uint16_t hsfc;
+	uint16_t timeout = 100 * 60;
+	uint8_t block_len;
+
+	if (addr < 0 || addr + len > flash->total_size * 1024) {
+		msg_perr("Request to write to an inaccessible memory address "
+			 "(addr=0x%x, len=%d).\n", addr, len);
+		return -1;
+	}
+
+	msg_pdbg("Writing %d bytes starting at 0x%06x.\n", len, addr);
+	/* clear FDONE, FCERR, AEL by writing 1 to them (if they are set) */
+	REGWRITE16(ICH9_REG_HSFS, REGREAD16(ICH9_REG_HSFS));
+
+	while (len > 0) {
+		ich_hwseq_set_addr(addr);
+		block_len = min(len, opaque_programmer->max_data_write);
+		ich_fill_data(buf, block_len, ICH9_REG_FDATA0);
+		hsfc = REGREAD16(ICH9_REG_HSFC);
+		hsfc &= ~HSFC_FCYCLE; /* clear operation */
+		hsfc |= (0x2 << HSFC_FCYCLE_OFF); /* set write operation */
+		hsfc &= ~HSFC_FDBC; /* clear byte count */
+		/* set byte count */
+		hsfc |= (((block_len - 1) << HSFC_FDBC_OFF) & HSFC_FDBC);
+		hsfc |= HSFC_FGO; /* start */
+		REGWRITE16(ICH9_REG_HSFC, hsfc);
+
+		if (ich_hwseq_wait_for_cycle_complete(timeout, block_len))
+			return -1;
+		addr += block_len;
+		buf += block_len;
+		len -= block_len;
+	}
+	return 0;
+}
 
 static int ich_spi_send_multicommand(struct spi_command *cmds)
 {
@@ -1325,6 +1525,14 @@ static const struct spi_programmer spi_programmer_ich9 = {
 	.write_256 = default_spi_write_256,
 };
 
+static const struct opaque_programmer opaque_programmer_ich_hwseq = {
+	.max_data_read = 64,
+	.max_data_write = 64,
+	.probe = ich_hwseq_probe,
+	.read = ich_hwseq_read,
+	.write = ich_hwseq_write,
+};
+
 int ich_init_spi(struct pci_dev *dev, uint32_t base, void *rcrb,
 			int ich_generation)
 {
@@ -1332,7 +1540,14 @@ int ich_init_spi(struct pci_dev *dev, uint32_t base, void *rcrb,
 	uint8_t old, new;
 	uint16_t spibar_offset, tmp2;
 	uint32_t tmp;
+	char *arg;
 	int desc_valid = 0;
+	struct ich_descriptors desc = {{ 0 }};
+	enum ich_spi_mode {
+		ich_auto,
+		ich_hwseq,
+		ich_swseq
+	} ich_spi_mode = ich_auto;
 
 	switch (ich_generation) {
 	case 7:
@@ -1399,6 +1614,22 @@ int ich_init_spi(struct pci_dev *dev, uint32_t base, void *rcrb,
 	case 9:
 	case 10:
 	default:		/* Future version might behave the same */
+		arg = extract_programmer_param("ich_spi_mode");
+		if (arg && !strcmp(arg, "hwseq")) {
+			ich_spi_mode = ich_hwseq;
+			msg_pspew("user selected hwseq\n");
+		} else if (arg && !strcmp(arg, "swseq")) {
+			ich_spi_mode = ich_swseq;
+			msg_pspew("user selected swseq\n");
+		} else if (arg && !strcmp(arg, "auto")) {
+			msg_pspew("user selected auto\n");
+			ich_spi_mode = ich_auto;
+		} else if (arg && !strlen(arg))
+			msg_pinfo("Missing argument for ich_spi_mode.\n");
+		else if (arg)
+			msg_pinfo("Unknown argument for ich_spi_mode: %s\n", arg);
+		free(arg);
+
 		tmp2 = mmio_readw(ich_spibar + ICH9_REG_HSFS);
 		msg_pdbg("0x04: 0x%04x (HSFS)\n", tmp2);
 		prettyprint_ich9_reg_hsfs(tmp2);
@@ -1484,14 +1715,41 @@ int ich_init_spi(struct pci_dev *dev, uint32_t base, void *rcrb,
 
 		msg_pdbg("\n");
 		if (desc_valid) {
-			struct ich_descriptors desc = {{ 0 }};
 			if (read_ich_descriptors_via_fdo(ich_spibar, &desc) ==
 			    ICH_RET_OK)
 				prettyprint_ich_descriptors(CHIPSET_ICH_UNKNOWN,
 							    &desc);
+			/* If the descriptor is valid and indicates multiple
+			 * flash devices we need to use hwseq to be able to
+			 * access the second flash device.
+			 */
+			if (ich_spi_mode == ich_auto && desc.content.NC != 0) {
+				msg_pinfo("Enabling hardware sequencing due to "
+					  "multiple flash chips detected.\n");
+				ich_spi_mode = ich_hwseq;
+			}
+		}
+
+		if (ich_spi_mode == ich_auto && ichspi_lock &&
+		    !ich_check_opcodes()) {
+			msg_pinfo("Enabling hardware sequencing because "
+				  "some important opcodes are locked.\n");
+			ich_spi_mode = ich_hwseq;
+		}
+
+		if (ich_spi_mode == ich_hwseq) {
+			if (!desc_valid) {
+				msg_perr("Hardware sequencing was requested "
+					 "but the flash descriptor is not "
+					 "valid. Aborting.\n");
+				return 1;
+			}
+			hwseq_data.size_comp0 = getFCBA_component_density(&desc, 0);
+			hwseq_data.size_comp1 = getFCBA_component_density(&desc, 1);
+			register_opaque_programmer(&opaque_programmer_ich_hwseq);
+		} else {
+			register_spi_programmer(&spi_programmer_ich9);
 		}
-		register_spi_programmer(&spi_programmer_ich9);
-		ich_init_opcodes();
 		break;
 	}
 
-- 
1.7.1





More information about the flashrom mailing list