/******************************************************************************* NAME biosSpi.cc VERSION %version: 9 % UPDATE DATE %date_modified: Wed Mar 16 19:56:59 2016 % PROGRAMMER %created_by: dhoyer % Copyright 2015-2017 NetApp, Inc. All Rights Reserved. DESCRIPTION: This source file provides the routines to read and write the SPI memory device for the BIOS SPI Programming instructions can be found in the Intel documents: Intel 5 Series Chipset and Intel 3400 Series Chipset SPI Programming Guide Ibex Peak Chipset *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include #include "biosSpi.h" #include "biosLib.h" const char* bios::BiosSpi::LOCK_NAME = "/var/run/eseries-bios-utils/bios.lock"; /** * \brief Default constructor * * \param[in] devSize is the size of the SPI devices * * \throws bios::BiosFail various messages indicating failure to initialize via logErr */ bios::BiosSpi::BiosSpi ( UINT32 devSize ) : m_HwLocked(true), m_SwLockCnt(0), m_SpiReg(0), m_MmapRcba(0), m_MemFd(-1), m_SpiSwLockFd(-1), m_DeviceSize(devSize), m_SelectedDev(bios::BiosSpi::STAGED_DEV) { // Make sure user is root if (getuid() != 0) { bios::logErr("This must be run as root"); } /* // Open file used in locking the process to other instances running m_SpiSwLockFd = open(LOCK_NAME, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); if (m_SpiSwLockFd < 0) { bios::logErr("Could not open %s as a process lock", LOCK_NAME); } */ // Read the PCI config register to get the RCBA UINT32 regMapAddr = 0; pciCfgOp(0, ICH3_LPC_DEV, 0, ICH3_LPC_RCBA, PCI_READ, 4, ®MapAddr); if (regMapAddr == 0) { bios::logErr("Could not read the RCBA from device 00:%02.2x.0", ICH3_LPC_DEV); } // Mask the address since the lower 14 bits don't apply to the address. regMapAddr &= 0xFFFFC000; // Now map that address so that it can be accessed m_MemFd = open("/dev/mem", O_RDWR | O_SYNC); if (m_MemFd < 0) { bios::logErr("Could not open /dev/mem to map SPI registers as RD/WR"); } m_MmapRcba = mmap(0, RCBA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, m_MemFd, static_cast(regMapAddr)); // If it failed, then log a message and close everything out if (m_MmapRcba == MAP_FAILED) { m_SpiReg = 0; close(m_MemFd); m_MemFd = -1; bios::logErr("Could not map the RCBA to user space phyAddr: 0x%08x size: 0x%08x", regMapAddr, static_cast(RCBA_SIZE)); } m_SpiReg = reinterpret_cast(reinterpret_cast(m_MmapRcba) + SPIBAR); } /** * \brief Default destructor * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ bios::BiosSpi::~BiosSpi ( ) { if (m_MmapRcba) { // Lock it back up if still unlocked... requires the mapped RCBA memory hwLock(); if (munmap(m_MmapRcba, RCBA_SIZE) == -1) bios::logErr("Could not unmap the RCBA to user space"); } if (m_MemFd >= 0) close(m_MemFd); if (m_SpiSwLockFd >= 0) close(m_SpiSwLockFd); } /** * \brief Show command * Debug command displaying various register information * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::show ( ) const { primaryRegionReg regionReg; const char *regName[] = { "Descriptor", "BIOS region", "ME region", "GbE region", "PDR"}; printf("spi from %p\n", m_SpiReg); printf(" gcs = 0x%x\n", *reinterpret_cast(reinterpret_cast(m_MmapRcba) + 0x3410)); printf(" bfpr = 0x%x\n", m_SpiReg->bfpr); printf(" hsfsts = 0x%x\n", m_SpiReg->hsfsts); printf(" hsfctl = 0x%x\n", m_SpiReg->hsfctl); printf(" faddr = 0x%x\n", m_SpiReg->faddr); printf(" fracc = 0x%x\n", m_SpiReg->fracc); printf(" ssfctl_sts = 0x%x\n", m_SpiReg->ssfctl_sts); printf(" preop = 0x%x\n", m_SpiReg->preop); printf(" optype = 0x%x\n", m_SpiReg->optype); for(int i = 0; i < 8; i++) printf(" opmenu[%d] = 0x%x\n", i, m_SpiReg->opmenu[i]); printf(" fpb = 0x%x\n", m_SpiReg->fpb); printf(" bios base 0x%x\n", (m_SpiReg->bfpr & 0xfff) << 12); printf(" bios limit 0x%x\n", ((m_SpiReg->bfpr & 0x0fff0000) >> (16-12)) + 0xfff); printf(" Regions:\n"); for (UINT region = 0; region < MAX_SPI_REGIONS; region++) { primaryRegionGet(static_cast(region), ®ionReg); printf(" freg%d %11s [0x%08x - 0x%08x]\n", region, regName[region], regionReg.base, regionReg.limit); } for (int i = 0; i < 5; i++) { UINT32 base = (m_SpiReg->fpr[i] & 0x1fff) << 12; UINT32 limit = ((m_SpiReg->fpr[i] & 0x1fff0000) >> (16-12)) + 0xfff; // If the base > limit or if the read protect and write protect disabled // clear the limit value if (base > limit || !((m_SpiReg->fpr[i] & (1<<31)) || (m_SpiReg->fpr[i] & (1<<15))) ) limit = 0; printf(" fpr%d protect [0x%08x - 0x%08x] %c%c raw: 0x%08X\n", i, base, limit, (m_SpiReg->fpr[i] & (1<<15)) ? 'R' : 'r', (m_SpiReg->fpr[i] & (1<<31)) ? 'W' : 'w', m_SpiReg->fpr[i]); } } /** * \brief program command programs image to SPI * * \param[in] src Buffer containing image to be programmed * \param[in] len length to program * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::program ( UINT32* src, UINT32 len ) { // Lock it so that another process cannot interfere //swLock(); // Make sure that SPI regions which won't be written to are write protected writeProtect(); if (!isBiosUpdateSupported()) bios::logErr("BIOS update is not supported with current version of running BIOS"); bios::logNorm("Erasing PDR region..."); erase(0, len); bios::logNorm("Performing blank check..."); blankCheck(0, len); bios::logNorm("Writing new image to PDR..."); write(0, src, len); bios::logNorm("Verifying..."); verifyProgram(src, len); bios::logNorm("Update complete!"); //swUnlock(); } /** * \brief erase command erases the SPI * This command erases the SPI starting as flash logical address (fla) * for the length (len) specified * * \param[in] fla Flash logical address to start from * \param[in] len length to erase * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::erase ( UINT32 fla, UINT32 len ) { if (m_SpiReg == 0) bios::logErr("SPI erase failed - not initialized"); UINT32 sectorSize = eraseBlockSize(fla); UINT32 offset = (m_SelectedDev == STAGED_DEV)? m_DeviceSize: 0; // Sanity check. Len must be a multiple of sectorSize if ((len % sectorSize) != 0) bios::logErr("%s: len must be a multiple of sectorSize", __FUNCTION__); hwUnlock(); waitForCompletion(); // First try the faster method. If that fails, revert to slower method bios::BiosSpi::BiosSpiOp spiOp(m_SpiReg, m_SelectedDev, m_DeviceSize); if (!spiOp.erase(fla, len)) { while (len >= sectorSize) { m_SpiReg->hsfsts = HSFS_AEL | HSFS_FCERR | HSFS_FDONE; m_SpiReg->faddr = fla + offset; m_SpiReg->hsfctl = HSFC_FERASE | HSFC_FGO; if (!waitForCompletion()) { hwLock(); bios::logErr("SPI Erase failed at fla 0x%08X (%x)", fla, m_SpiReg->hsfsts); } fla += sectorSize; len -= sectorSize; } } hwLock(); } /** * \brief write command writes image to SPI * It assumes that the devices was already erased * * \param[in] fla Flash logical address to start from * \param[in] src Buffer containing image to be programmed * \param[in] len length to program * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::write ( UINT32 fla, UINT32* src, UINT32 len ) { if (m_SpiReg == 0) bios::logErr("SPI Write failed - not initialized"); // Unlock SPI for writing hwUnlock(); volatile UINT32* dAddr = m_SpiReg->data; UINT32 offset = (m_SelectedDev == STAGED_DEV)? m_DeviceSize: 0; // Make sure it is ready waitForCompletion(); // Sanity check. Len must be a multiple of BLOCK_SIZE if ((len % BLOCK_SIZE) != 0) bios::logErr("%s: length must be a multiple of BLOCK_SIZE", __FUNCTION__); // write in BLOCK_SIZE byte chunks while (len >= BLOCK_SIZE) { m_SpiReg->hsfsts = HSFS_AEL | HSFS_FCERR | HSFS_FDONE; m_SpiReg->faddr = fla + offset; for (UINT32 i=0; i < BLOCK_SIZE/sizeof(UINT32); i++) dAddr[i] = *src++; m_SpiReg->hsfctl = HSFC_BYTES64 | HSFC_FWRITE | HSFC_FGO; // If it fails to complete, then indicate error if (!waitForCompletion()) { hwLock(); bios::logErr("SPI Write failed at fla 0x%08X (%x)", fla, m_SpiReg->hsfsts); } fla += BLOCK_SIZE; len -= BLOCK_SIZE; } hwLock(); } /** * \brief read command reads image from SPI * * \param[in] fla Flash logical address to start from * \param[out] dest Buffer containing buffer to read image into * \param[in] len length to read * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::read ( UINT32 fla, UINT32* dest, UINT32 len ) { UINT32 i; if (m_SpiReg == 0) bios::logErr("SPI read failed - not initialized"); //swLock(); volatile UINT32* dAddr = m_SpiReg->data; UINT32 offset = (m_SelectedDev == STAGED_DEV)? m_DeviceSize: 0; waitForCompletion(); // read in BLOCK_SIZE byte chunks while (len >= BLOCK_SIZE) { m_SpiReg->hsfsts = HSFS_AEL | HSFS_FCERR | HSFS_FDONE; m_SpiReg->faddr = fla + offset; m_SpiReg->hsfctl = HSFC_BYTES64 | HSFC_FREAD | HSFC_FGO; if (!waitForCompletion()) bios::logErr("Timed out waiting for completion or error detected at 0x%08X", fla + offset); fla += BLOCK_SIZE; len -= BLOCK_SIZE; for(i = 0; i < BLOCK_SIZE/sizeof(UINT32); i++) *dest++ = dAddr[i]; } // read the last chunk if not aligned on BLOCK_SIZE if (len > 0) { m_SpiReg->hsfsts = HSFS_AEL | HSFS_FCERR | HSFS_FDONE; m_SpiReg->faddr = fla + offset; m_SpiReg->hsfctl = ((len-1)<<8) | HSFC_FREAD | HSFC_FGO; if (!waitForCompletion()) bios::logErr("Timed out waiting for completion or error detected at 0x%08X", fla + offset); for (i = 0; i < len/sizeof(UINT32); i++) { *dest++ = dAddr[i]; len -= sizeof(UINT32); } if (len > 0) { volatile UINT8 *bsrc = reinterpret_cast(&dAddr[i]); UINT8 *bdst = reinterpret_cast(dest); while (len-- > 0) *bdst++ = *bsrc++; } } //swUnlock(); } /** * \brief read command reads a particular region from SPI * It assumes that dest points to a buffer the size * of the BIOS. * * \param[in] region Region to be read * \param[out] dest Buffer containing buffer to read image into * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::readRegion ( bios::BiosSpi::SPIRegion region, UINT32* dest ) { primaryRegionReg regInfo; if (!primaryRegionGet(region, ®Info)) bios::logErr("Could not get region info from region %d", region); read(regInfo.base, &dest[regInfo.base / sizeof(*dest)], regInfo.limit - regInfo.base + 1); } /** * \brief blankCheck command verifies that SPI device is cleared * * \param[in] fla Flash logical address to start from * \param[in] len length to verify * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::blankCheck ( UINT32 fla, UINT32 len ) { UINT32 block[BLOCK_SIZE / sizeof(UINT32)]; while (len > 0) { UINT32 size = (len < BLOCK_SIZE ? len : BLOCK_SIZE); read(fla, block, size); for (UINT32 i = 0; i < size / sizeof(UINT32); i++) { if(block[i] != CELL_CLEARED) { bios::logErr("SPI blank check failed: fla 0x%08X Expected: 0x%08X Actual: 0x%08X", static_cast(fla + (i * sizeof(UINT32))), CELL_CLEARED, block[i]); } } len -= size; fla += size; } } /** * \brief eraseBlockSize command reports number of bytes to be erased per command * * \return number of bytes that will be erased per erase command */ UINT32 bios::BiosSpi::eraseBlockSize ( UINT32 fla ) const { UINT32 sectorSize; // Must set address register before reading sector size m_SpiReg->faddr = fla; switch((m_SpiReg->hsfsts & HSFS_BERASE)>>3) { case 0: default: sectorSize = 256; break; case 1: sectorSize = 4096; break; case 2: sectorSize = 8192; break; case 3: sectorSize = 65536; break; } return sectorSize; } /** * \brief verifyProgram command verifies that SPI device is accurately programmed * * \param[in] image Pointer to image of the BIOS in memory * \param[in] len length to verify * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ void bios::BiosSpi::verifyProgram ( UINT32 *image, UINT32 len ) { UINT32 i; UINT32 fla = 0; UINT32 sectorSize = eraseBlockSize(fla); /* // Using aligned function to make sure it is long word aligned UINT32* block = static_cast(aligned_alloc(sizeof(UINT32), sectorSize)); if (!block) { bios::logErr("Could not allocated memory for block comparison"); } // This with automatically free on exit utl::ScopeGuard bufferGuard = utl::makeMallocMemGuard(&block); (void)bufferGuard; */ UINT32* block = (UINT32*)malloc(sizeof(UINT8) * sectorSize); while (len >= sectorSize) { read(fla, block, sectorSize); for (i = 0; i < sectorSize/sizeof(UINT32); i++, image++) { if(block[i] != *image) bios::logErr("Validation error at fla 0x%08x: Exp 0x%08x Act: 0x%08x", static_cast(fla + (i * sizeof(UINT32))), *image, block[i]); } len -= sectorSize; fla += sectorSize; } } /** * \brief primaryRegionGet command retrieves information for the requested region * * \param[in] region Enum value of the region that information is requested on * \param[out] regData pointer to the structure where the region data should be put * * \return true is successful and false otherwise */ bool bios::BiosSpi::primaryRegionGet ( bios::BiosSpi::SPIRegion region, primaryRegionReg* regData ) const { if (region >= MAX_SPI_REGIONS) return false; if (m_SpiReg == 0) return false; volatile UINT32* freg = &(m_SpiReg->freg0); // The lower 12 bits contain the base address which needs to be shifted by 12 bits right // Bits 16-28 contain the limit address which has to be shifted down and 0xfff added on // Since those bits are assumed regData->base = (freg[region] & 0x1fff) << 12; regData->limit = ((freg[region] & 0x1fff0000) >> (16-12)) + 0xfff; // If the base address ends up being greater than the limit, that means the region has no // data so zero out the limit if (regData->base > regData->limit) regData->limit = 0; return true; } /** * \brief hwLock command locks the SPI region against writes * */ void bios::BiosSpi::hwLock ( ) { if (!m_HwLocked) { UINT8 regVal; pciCfgOp(0, ICH3_LPC_DEV, 0, SPI_LOCK_REG, PCI_READ, 1, ®Val); regVal &= ~1; // Clear LSbit pciCfgOp(0, ICH3_LPC_DEV, 0, SPI_LOCK_REG, PCI_WRITE, 1, ®Val); } m_HwLocked = true; } /** * \brief hwUnlock command unlocks the SPI region for writes * */ void bios::BiosSpi::hwUnlock ( ) { if (m_HwLocked) { UINT8 regVal; pciCfgOp(0, ICH3_LPC_DEV, 0, SPI_LOCK_REG, PCI_READ, 1, ®Val); regVal |= 1; // Set LSbit pciCfgOp(0, ICH3_LPC_DEV, 0, SPI_LOCK_REG, PCI_WRITE, 1, ®Val); } m_HwLocked = false; } /** * \brief swLock command locks against other instances running * */ void bios::BiosSpi::swLock ( ) { // Now lock the process if (m_SwLockCnt++ == 0) { // This lock will fail if another instance is already running if (lockf(m_SpiSwLockFd, F_TLOCK, 0) < 0) { bios::logErr("Could not lock %s... possibly conflicting with another instance", LOCK_NAME); } } } /** * \brief swUnlock command releases locks against other instances running * Note that when the program exits, this lock is automatically relinquished * so there is no compelling reason to worry about catching exceptions * */ void bios::BiosSpi::swUnlock ( ) { // Now unlock the process if (m_SwLockCnt > 0) { // Decrement counter and unlock if it reached zero if (--m_SwLockCnt == 0) { if (lockf(m_SpiSwLockFd, F_ULOCK, 0) < 0) { bios::logErr("Could not unlock %s", LOCK_NAME); } } } } /** * \brief writeProtect command write protects all SPI regions except for * the PDR one to prevent accidental writes. * */ void bios::BiosSpi::writeProtect ( ) const { volatile UINT32* freg = &(m_SpiReg->freg0); for (UINT region = 0; region < MAX_SPI_REGIONS; region++) { // Skip the PDR region since that is the one used to write to the // secondary device if (region == SPI_PDR) continue; primaryRegionReg regionReg; primaryRegionGet(static_cast(region), ®ionReg); // Write protect this register and make sure it is readable but only // do so if the limit is non-zero (meaning this region takes up some space) if (regionReg.limit != 0) m_SpiReg->fpr[region] = (freg[region] | REG_WRITE_PROT) & ~REG_READ_PROT; } } /** * \brief isBiosUpdateSupported command determines if a BIOS update is support * by this platform. This is identified by the PDR region being the * size of the remaining regions combined * */ bool bios::BiosSpi::isBiosUpdateSupported ( ) const { UINT32 biosImageSize = 0; UINT32 pdrRegionSize = 0; for (UINT region = 0; region < MAX_SPI_REGIONS; region++) { primaryRegionReg regionReg; primaryRegionGet(static_cast(region), ®ionReg); // If the limit is zero then the region is size is zero. // Otherwise the region size is the difference between limit and base + 1 UINT32 regionSize; if (regionReg.limit == 0) regionSize = 0; else regionSize = regionReg.limit - regionReg.base + 1; if (region == SPI_PDR) pdrRegionSize = regionSize; else biosImageSize += regionSize; } return (pdrRegionSize >= biosImageSize); } /** * \brief waitForComplete waits up to MAX_WAIT for SPI operation to complete * * \return true if successful, false if it timed out */ bool bios::BiosSpi::waitForCompletion ( ) const { static const int MAX_WAIT = 100000; // Max wait time is 100 msec int waitCnt = 0; UINT32 stat; // wait until free or the wait time has expired while ( (((stat = m_SpiReg->hsfsts) & HSFS_SCIP) != 0) && (waitCnt++ < MAX_WAIT) ) { usleep(1); } // If time expired or if FCERR or AEL bit are set // then return an error if (waitCnt >= MAX_WAIT || (stat & (HSFS_FCERR | HSFS_AEL))) return false; else return true; } /** * \brief pciCfgOp command performs requested PCI configuration operation * * \param[in] bus is the PCI bus number * \param[in] dev is the PCI device number * \param[in] func is the PCI function number * \param[in] offset is the offset into the PCI config space * \param[in] direction indicates if this is a read or write operation * \param[in] size indicates size of operation (1/2/4) * \param[in/out] value pointer value to be written or read back * * \throws bios::BiosFail various messages indicating failure to initialize via logErr */ void bios::BiosSpi::pciCfgOp ( UINT32 bus, UINT32 dev, UINT32 func, UINT32 offset, bios::BiosSpi::PCI_OP_DIRECTION direction, UINT32 size, void* value ) { char devicePath[256]; // Should be more than adequate given the string is fixed length snprintf(devicePath, sizeof(devicePath), "/sys/bus/pci/devices/0000:%2.2x:%2.2x.%1.1x/config", bus, dev, func); // Open up for both read and write int fd = ::open(devicePath, O_RDWR | O_SYNC); if (fd == -1) bios::logErr("Could not access %s PCI config space", devicePath); // Add scope guard to close file descriptor on exiting scope //utl::ScopeGuard fdGuard = utl::makeScopeGuard(utl::FdClose(fd)); // Move to the offset location which was requested if (::lseek(fd, offset, SEEK_SET) == static_cast(-1)) bios::logErr("Could not go to offset %d on PCI device %02.2x:%02.2x:%01.1x", offset, bus, dev, func); // Now perform actual read/write operation if (direction == PCI_WRITE) { if (::write(fd, value, size) <= 0) bios::logErr("Could not write to offset %d on PCI device %02.2x:%02.2x:%01.1x", offset, bus, dev, func); } else // Assuming PCI_READ since reads should be "safe" { if (::read(fd, value, size) <= 0) bios::logErr("Could not read from offset %d on PCI device %02.2x:%02.2x:%01.1x", offset, bus, dev, func); } } // Table of SPI opcodes to be programmed to the device // These codes are using in programming the SPI chip const bios::BiosSpi::BiosSpiOp::opCode bios::BiosSpi::BiosSpiOp::spiOps[] = { {JEDEC_BYTE_PROGRAM, SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS}, // Write Byte {JEDEC_READ, SPI_OPCODE_TYPE_READ_WITH_ADDRESS}, // Read Data {JEDEC_64K, SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS}, // Erase Sector 64k {JEDEC_RDSR, SPI_OPCODE_TYPE_READ_NO_ADDRESS}, // Read Device Status Reg {JEDEC_RDID, SPI_OPCODE_TYPE_READ_NO_ADDRESS}, // Read JDEC ID {JEDEC_WRSR, SPI_OPCODE_TYPE_WRITE_NO_ADDRESS}, // Write Status Register {JEDEC_REMS, SPI_OPCODE_TYPE_READ_WITH_ADDRESS}, // Read Electronic Manufacturer Signature {JEDEC_SFDP, SPI_OPCODE_TYPE_READ_WITH_ADDRESS}, // Read Serial Flash Discoverable Parameters }; /** * \brief Default constructor * Sets up SPI registers as needed for faster operations * * \param[in] spiReg SPI register address * \param[in] device Which device operations will happen on * \param[in] size Size of SPI device * * \throws bios::BiosFail various messages indicating failure to initialize via logErr */ bios::BiosSpi::BiosSpiOp::BiosSpiOp ( volatile bios::BiosSpi::spiReg* spiReg, bios::BiosSpi::SPI_DEV_SELECT device, UINT32 size ) : m_SpiReg(spiReg), m_Stored(false), m_DevSelect(device), m_BiosSize(size) { // A little sanity checking if (!m_SpiReg) bios::logErr("SPI register is not set up"); // Store away current settings m_Preop = m_SpiReg->preop; m_Optype = m_SpiReg->optype; for (UINT i = 0; i < MAX_OPS; i++) { m_Opmenu[i] = m_SpiReg->opmenu[i]; } m_Stored = true; /* Program Prefix Opcodes */ m_SpiReg->preop = JEDEC_WREN | (JEDEC_EWSR << 8); /* Program Opcode Types 0 - 7 */ UINT16 optype = 0; for (UINT i = 0; i < MAX_OPS; i++) { optype |= spiOps[i].spiType << (i * 2); } m_SpiReg->optype = optype; /* Program Allowable Opcodes 0 - 7 */ for (UINT i = 0; i < MAX_OPS; i++) { m_SpiReg->opmenu[i] = spiOps[i].opcode; } } /** * \brief Default destructor * Restores SPI registers back to original state * */ bios::BiosSpi::BiosSpiOp::~BiosSpiOp ( ) { // Only restore if stored in the first place if (m_Stored) { // Restore prior settings m_SpiReg->preop = m_Preop; m_SpiReg->optype = m_Optype; for (UINT i = 0; i < MAX_OPS; i++) { m_SpiReg->opmenu[i] = m_Opmenu[i]; } } } /** * \brief erase command erases the SPI in the fastest method if possible * This command erases the SPI starting as flash logical address (fla) * for the length (len) specified * * \param[in] fla Flash logical address to start from * \param[in] len length to erase * * \return bool true if successful */ bool bios::BiosSpi::BiosSpiOp::erase ( UINT32& fla, UINT32& len ) const { struct opcodeSize { UINT32 opcode; UINT32 size; }; static const opcodeSize opcodeList[] = { { JEDEC_4K, 4 * 1024 }, { JEDEC_32K, 32 * 1024 }, { JEDEC_64K, 64 * 1024 }, { JEDEC_CHIP1, JEDEC_FULL_ERASE_SIZE }, { JEDEC_CHIP2, JEDEC_FULL_ERASE_SIZE } }; const opCode* selectedOpCode = 0; UINT eraseSize = 0; for (UINT i = 0; i < MAX_OPS && selectedOpCode == 0; i++) { for (UINT j = 0; j < sizeof(opcodeList) / sizeof(opcodeList[0]); j++) { if (opcodeList[j].opcode == spiOps[i].opcode) { eraseSize = opcodeList[j].size; selectedOpCode = &spiOps[i]; break; } } } // No need to validate eraseSize as it will be ignored if selectedOpCode is null if (selectedOpCode == 0) { bios::logWarn("Could not find opcode to erase device"); return false; } // Make sure that the length is a multiple of the block erase size if ((len % eraseSize) != 0) { bios::logWarn("Bios Size (%x) is not multiple of erase size (%x)", len, eraseSize); return false; } // Now loop through the erase blocks and erase everything while (len >= eraseSize) { // Run the opcode. If it fails, then return false to allow caller to decide recourse UINT32 offset = (m_DevSelect == bios::BiosSpi::STAGED_DEV)? m_BiosSize: 0; if (!runOp(bios::BiosSpi::BiosSpiOp::SPI_USE_FIRST_PREOP, *selectedOpCode, fla + offset, 0, 0)) return false; // Now wait until the operation is totally finished try { // 60 seconds - should never come close int waitCnt = (60 * 1000 * 1000); while ( ((readStatusReg() & SPI_SR_WIP) != 0) && (--waitCnt != 0)) { usleep(1); } if (waitCnt == 0) { bios::logWarn("Read Status timed out..."); return false; } } catch(std::exception& e) { std::cout << e.what() << std::endl; return false; } catch(...) { std::cout << "Unhandled exception!!!" << std::endl; return false; } fla += eraseSize; len -= eraseSize; } return true; } /** * \brief readStatusReg command reads the status register from the SPI device * * \return UINT8 value of the status register * * \throws bios::BiosFail various messages indicating failure conditions via logErr */ UINT8 bios::BiosSpi::BiosSpiOp::readStatusReg ( ) const { // Find the read status register opcode const opCode* selectedOpCode = 0; for (UINT i = 0; i < MAX_OPS; i++) { if (spiOps[i].opcode == JEDEC_RDSR) { selectedOpCode = &spiOps[i]; break; } } // This should never happen... but if it does, throw an exception if (selectedOpCode == 0) { bios::logErr("Could not find opcode to get status register"); } // Buffer containing the read status. This will read the status twice // where this routine only uses the first byte UINT8 status[2] = {0}; // Must determine offset since the command needs to go to the right device. // Otherwise address means nothing. UINT32 offset = (m_DevSelect == bios::BiosSpi::STAGED_DEV)? m_BiosSize: 0; if (!runOp(bios::BiosSpi::BiosSpiOp::SPI_NO_PREOP, *selectedOpCode, offset, sizeof(status), status)) bios::logErr("Failed to read the SPI status register"); return status[0]; } /** * \brief runOp command runs the requested operation. It will return the data if read * command into the "data" buffer * * \param[in] preOp The PRE-Op instruction for the particular command * \param[in] op Structure containing necessary command op information * \param[in] fla Flash logic address on which to perform the operation * \param[in] len Length of the data coming in or going out * \param[in/out] data The data to be written or buffer to receive read data * * \return bool true if successful */ bool bios::BiosSpi::BiosSpiOp::runOp ( bios::BiosSpi::BiosSpiOp::SPI_PREOP_MODE preOp, const opCode& op, UINT32 fla, UINT32 len, UINT8* data ) const { static const int STANDARD_WAIT = (60 * 1000); // 60 milliseconds static const int PREOP_WAIT = (60 * 1000 * 1000); // 60 seconds INT32 waitCnt = 0; // Is it a write command bool writeCmd = false; if ((op.spiType == SPI_OPCODE_TYPE_WRITE_NO_ADDRESS) || (op.spiType == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS)) { writeCmd = true; } // Determine which opmenu this opcode resides UINT32 opIndex; for (opIndex = 0; opIndex < MAX_OPS; opIndex++) { if (m_SpiReg->opmenu[opIndex] == op.opcode) break; } if (opIndex == MAX_OPS) { bios::logWarn("Opcode 0x%x not found in opmenu", op.opcode); return false; } // Validate the length parameter if (len > sizeof(m_SpiReg->data)) { bios::logWarn("Opcode length (%x) invalid", len); return false; } // Make sure no other ops are in progress waitCnt = STANDARD_WAIT; while ( ((m_SpiReg->ssfctl_sts & SSFS_SCIP) != 0) && (--waitCnt != 0)) { usleep(1); } if (waitCnt == 0) { bios::logWarn("SW method never came ready..."); return false; } // Set the address m_SpiReg->faddr = fla; // Write out the data if a write command if (writeCmd && (len != 0)) { // We have to copy byte by byte but since the interface is UINT32, // the copy will be done in blocks UINT32 i; UINT32 sizeofData = sizeof(m_SpiReg->data[0]); UINT32 writeVal = 0; for (i = 0; i < len; i++) { if ((i % sizeofData) == 0) writeVal = 0; writeVal |= data[i] << ((i % sizeofData) * 8); // If this is the last byte, then write to register if ((i % sizeofData) == (sizeofData - 1)) m_SpiReg->data[(i - (i % sizeofData))/sizeofData] = writeVal; } // If it ended on a non-aligned location, then write out last part i--; if ((i % sizeofData) != (sizeofData - 1)) m_SpiReg->data[(i - (i % sizeofData)) / sizeofData] = writeVal; } // Get current status/control register masking off bits which // are to be altered by this routine UINT32 tempReg = m_SpiReg->ssfctl_sts & SSFS_SSFC_MASK; // Clear any errors which might have been there previously tempReg |= (SSFS_FDONE | SSFS_FCERR); m_SpiReg->ssfctl_sts = tempReg; // Set data byte count (DBC) and data cycle bit (DS) if (len != 0) { tempReg |= SSFC_DS; tempReg |= ((len - 1) << SSFC_DBC_SHIFT); } // Put in the opcode index into the instruction tempReg |= (opIndex << SSFC_OPCODE_SHIFT); waitCnt = STANDARD_WAIT; switch (preOp) { case SPI_USE_SECOND_PREOP: // Select second preop tempReg |= SSFC_SPOP; // And fall through case SPI_USE_FIRST_PREOP: // Atomic command (preop+op) tempReg |= SSFC_ACS; waitCnt = PREOP_WAIT; break; case SPI_NO_PREOP: // Do nothing break; } // Set the GO bit and write to register m_SpiReg->ssfctl_sts = tempReg | SSFC_SCGO; // wait until free or the wait time has expired UINT32 stat; while ( (((stat = m_SpiReg->ssfctl_sts) & (SSFS_FDONE | SSFS_FCERR)) == 0) && (--waitCnt != 0) ) { usleep(1); } // If time expired or if FCERR bit is set then return an error if (waitCnt == 0 || (stat & SSFS_FCERR)) { bios::logWarn("SW method timed out..."); return false; } if (!writeCmd && (len != 0)) { // We have to copy byte by byte but since the interface is UINT32, // the copy will be done in blocks UINT32 val = 0; for (UINT32 i = 0; i < len; i++) { UINT32 modulo = i % sizeof(m_SpiReg->data[0]); if (modulo == 0) val = m_SpiReg->data[i / sizeof(m_SpiReg->data[0])]; data[i] = (val >> (modulo * 8)) & 0xff; } } return true; }