Author: stefanct Date: Tue Nov 8 11:55:54 2011 New Revision: 1461 URL: http://flashrom.org/trac/flashrom/changeset/1461
Log: ichspi: add support for Intel Hardware Sequencing
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/
Besides adding hwseq this patch also introduces these unrelated changes:
- Fix enable_flash_ich_dc_spi to pass ERROR_FATAL from ich_init_spi. The whole error handling looks a bit odd to me, so this patch does change very little. Also, it does not touch the tunnelcreek method, which should be refactored anyway.
- Add null-pointer guards to find_opcode and find_preop to matches the other opcode methods better: curopcodes == NULL has some meaning and is actively used/checked in other functions.
TODO: adding real documentation when we have a directory for it
Signed-off-by: Stefan Tauner stefan.tauner@student.tuwien.ac.at Acked-by: Carl-Daniel Hailfinger c-d.hailfinger.devel.2006@gmx.net
Modified: trunk/chipset_enable.c trunk/flashrom.8 trunk/ichspi.c
Modified: trunk/chipset_enable.c ============================================================================== --- trunk/chipset_enable.c Mon Nov 7 00:51:09 2011 (r1460) +++ trunk/chipset_enable.c Tue Nov 8 11:55:54 2011 (r1461) @@ -491,7 +491,7 @@ static int enable_flash_ich_dc_spi(struct pci_dev *dev, const char *name, enum ich_chipset ich_generation) { - int ret; + int ret, ret_spi; uint8_t bbs, buc; uint32_t tmp, gcs; void *rcrb; @@ -569,10 +569,12 @@ }
/* This adds BUS_SPI */ - if (ich_init_spi(dev, tmp, rcrb, ich_generation) != 0) { - if (!ret) - ret = ERROR_NONFATAL; - } + ret_spi = ich_init_spi(dev, tmp, rcrb, ich_generation); + if (ret_spi == ERROR_FATAL) + return ret_spi; + + if (ret || ret_spi) + ret = ERROR_NONFATAL;
return ret; }
Modified: trunk/flashrom.8 ============================================================================== --- trunk/flashrom.8 Mon Nov 7 00:51:09 2011 (r1460) +++ trunk/flashrom.8 Tue Nov 8 11:55:54 2011 (r1461) @@ -303,6 +303,26 @@ 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
Modified: trunk/ichspi.c ============================================================================== --- trunk/ichspi.c Mon Nov 7 00:51:09 2011 (r1460) +++ trunk/ichspi.c Tue Nov 8 11:55:54 2011 (r1461) @@ -423,6 +423,11 @@ { int a;
+ if (op == NULL) { + msg_perr("\n%s: null OPCODES pointer!\n", __func__); + return -1; + } + for (a = 0; a < 8; a++) { if (op->opcode[a].opcode == opcode) return a; @@ -435,6 +440,11 @@ { int a;
+ if (op == NULL) { + msg_perr("\n%s: null OPCODES pointer!\n", __func__); + return -1; + } + for (a = 0; a < 2; a++) { if (op->preop[a] == preop) return a; @@ -561,6 +571,31 @@ }
/* + * Returns -1 if at least one mandatory opcode is inaccessible, 0 otherwise. + * FIXME: this should also check for + * - at least one probing opcode (RDID (incl. AT25F variants?), REMS, RES?) + * - at least one erasing opcode (lots.) + * - at least one program opcode (BYTE_PROGRAM, AAI_WORD_PROGRAM, ...?) + * - necessary preops? (EWSR, WREN, ...?) + */ +static int ich_missing_opcodes() +{ + uint8_t ops[] = { + JEDEC_READ, + 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 -1; + i++; + } + return 0; +} + +/* * Try to set BBAR (BIOS Base Address Register), but read back the value in case * it didn't stick. */ @@ -1066,7 +1101,11 @@ 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) { @@ -1117,8 +1156,8 @@ if (!timeout) { addr = REGREAD32(ICH9_REG_FADDR) & 0x01FFFFFF; msg_perr("Timeout error between offset 0x%08x and " - "0x%08x + %d (=0x%08x)!\n", - addr, addr, len - 1, addr + len - 1); + "0x%08x (= 0x%08x + %d)!\n", + addr, addr + len - 1, addr, len - 1); prettyprint_ich9_reg_hsfs(hsfs); prettyprint_ich9_reg_hsfc(REGREAD16(ICH9_REG_HSFC)); return 1; @@ -1127,7 +1166,7 @@ if (hsfs & HSFS_FCERR) { addr = REGREAD32(ICH9_REG_FADDR) & 0x01FFFFFF; msg_perr("Transaction error between offset 0x%08x and " - "0x%08x (=0x%08x + %d)!\n", + "0x%08x (= 0x%08x + %d)!\n", addr, addr + len - 1, addr, len - 1); prettyprint_ich9_reg_hsfs(hsfs); prettyprint_ich9_reg_hsfc(REGREAD16(ICH9_REG_HSFC)); @@ -1135,7 +1174,184 @@ } 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) { @@ -1300,6 +1516,15 @@ .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, + .erase = ich_hwseq_block_erase, +}; + int ich_init_spi(struct pci_dev *dev, uint32_t base, void *rcrb, enum ich_chipset ich_gen) { @@ -1307,13 +1532,20 @@ 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;
ich_generation = ich_gen;
switch (ich_generation) { case CHIPSET_ICH_UNKNOWN: - return -1; + return ERROR_FATAL; case CHIPSET_ICH7: case CHIPSET_ICH8: spibar_offset = 0x3020; @@ -1330,6 +1562,8 @@ /* Assign Virtual Address */ ich_spibar = rcrb + spibar_offset;
+ ich_init_opcodes(); + switch (ich_generation) { case CHIPSET_ICH7: msg_pdbg("0x00: 0x%04x (SPIS)\n", @@ -1369,10 +1603,31 @@ } ich_set_bbar(0); register_spi_programmer(&spi_programmer_ich7); - ich_init_opcodes(); break; case CHIPSET_ICH8: 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_perr("Missing argument for ich_spi_mode.\n"); + free(arg); + return ERROR_FATAL; + } else if (arg) { + msg_perr("Unknown argument for ich_spi_mode: %s\n", + arg); + free(arg); + return ERROR_FATAL; + } + free(arg); + tmp2 = mmio_readw(ich_spibar + ICH9_REG_HSFS); msg_pdbg("0x04: 0x%04x (HSFS)\n", tmp2); prettyprint_ich9_reg_hsfs(tmp2); @@ -1458,14 +1713,41 @@
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_missing_opcodes()) { + msg_pinfo("Enabling hardware sequencing because " + "some important opcode is 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 ERROR_FATAL; + } + 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; }