/***************************************************************************** * File: fm10k.c * Creation Date: December 20, 2020 * * Copyright (c) 2014 - 2015, Intel Corporation * Copyright (c) 2021, FM10K-Documentation Contributors * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #include "fm10k.h" #include #include #include #include #include #include #include #include KNOWN_FLASH_DEVICE KNOWN_FLASH_DEVICE_LIST[] = { {{0x0101271f}, "Adesto AT45DB321E 32-Mbit", 32}, // Present on Silicom PE3100G2DQiRM-QX4 {{0x001540ef}, "Winbond W25Q16JV 16-Mbit", 16}, // Present on Intel FM10420-100GbE-QDA2 {{0}, "", 0} }; void ReadRegister32(uintptr_t mem, uint32_t addr, uint32_t *value) { *value = *(((volatile uint32_t *) mem) + addr); } void WriteRegister32(uintptr_t mem, uint32_t addr, uint32_t value) { *(((volatile uint32_t *) mem) + addr) = value; //If returning instantly this will caused missed writes. nanosleep wait of 1 nsec was too much. Busy wait works #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-but-set-variable" volatile uint32_t dummy; for (uint32_t i = 0; i < 100; ++i) { dummy = i; } #pragma GCC diagnostic pop } uint32_t get_interval_diff(struct timeval *begin, struct timeval *end) { struct timeval endT; struct timeval diff; if (end == NULL) { gettimeofday(&endT, NULL); } else { endT.tv_sec = end->tv_sec; endT.tv_usec = end->tv_usec; } diff.tv_sec = endT.tv_sec - begin->tv_sec; diff.tv_usec = endT.tv_usec - begin->tv_usec; return (diff.tv_sec * 1000 + diff.tv_usec / 1000); } uint32_t fm10k_mem_spi_SetCtrlReg(uintptr_t mem, SPI_CTRL value) { struct timeval startTime; uint8_t isTimeout = 0; SPI_CTRL spiCtrl = {0}; WriteRegister32(mem, FM10K_REGISTER_SPI_CTRL, value.value); gettimeofday(&startTime, NULL); do { if (isTimeout) { printf("Timeout waiting for SPI_CTRL.Busy 0x%02x\n", spiCtrl.value); return 1; } if (get_interval_diff(&startTime, NULL) > 50) { isTimeout = 1; } ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, &spiCtrl.value); } while (spiCtrl.fields.Busy); /* write back SPI_CTRL with command = 0 */ spiCtrl.fields.Command = 0; WriteRegister32(mem, FM10K_REGISTER_SPI_CTRL, spiCtrl.value); return 0; } void fm10k_mem_spi_Enable(uintptr_t mem) { //ENABLE SPI SPI_CTRL spiCtrl; ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, &spiCtrl.value); /* keep current freq setting and set SPI Enable */ SPI_CTRL newSpiCtrl; newSpiCtrl.value = 0; newSpiCtrl.fields.Freq = spiCtrl.fields.Freq; newSpiCtrl.fields.Enable = 1; WriteRegister32(mem, FM10K_REGISTER_SPI_CTRL, newSpiCtrl.value); } void fm10k_mem_spi_Disable(uintptr_t mem) { //DISABLE SPI SPI_CTRL spiCtrl; ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, &spiCtrl.value); /* keep current freq setting and set SPI Enable = 0 */ SPI_CTRL newSpiCtrl; newSpiCtrl.value = 0; newSpiCtrl.fields.Freq = spiCtrl.fields.Freq; WriteRegister32(mem, FM10K_REGISTER_SPI_CTRL, newSpiCtrl.value); } //Error if result.value is 0x00 SPI_COMMAND_READ_MANUFACTURER_RESULT fm10k_mem_spi_ReadManufacturerData(uintptr_t mem) { SPI_COMMAND_READ_MANUFACTURER_RESULT result = {0}; SPI_CTRL currentSpiCtrl; SPI_CTRL spiCtrl = {0}; uint32_t header; int32_t freq; fm10k_mem_spi_Enable(mem); ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, ¤tSpiCtrl.value); if (FM10K_SPI_FREQ_KHZ > 0) { freq = ((100000 / (int) FM10K_SPI_FREQ_KHZ) / 2) - 1; if (freq < 0) { freq = 0; } spiCtrl.fields.Freq = freq; } spiCtrl.fields.Enable = currentSpiCtrl.fields.Enable; /* header: command (1 byte: READ_BYTES) */ header = FM10K_SPI_HEADER_COMMAND_READ_MANUFACTURER_INFORMATION; WriteRegister32(mem, FM10K_REGISTER_SPI_HEADER, header); /* first loop only: set send header flag. Following loops: shift only data. */ spiCtrl.fields.Command |= 0x1; spiCtrl.fields.Command |= 0x4; spiCtrl.fields.HeaderSize = 1; spiCtrl.fields.DataSize = 0x4 & 3; if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return result; } uint32_t rxData; /* get data */ ReadRegister32(mem, FM10K_REGISTER_SPI_RX_DATA, &rxData); result.value = ((rxData >> 24) & 0xff) | ((rxData << 8) & 0xff0000) | ((rxData >> 8) & 0xff00) | ((rxData << 24) & 0xff000000); /* release CS */ spiCtrl.fields.Command = 0x8; if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { result.value = 0; return result; } fm10k_mem_spi_Disable(mem); return result; } uint32_t fm10k_mem_spi_ReadFlash(uintptr_t mem, uint32_t address, uint8_t *data, uint32_t len) { SPI_CTRL currentSpiCtrl; SPI_CTRL spiCtrl = {0}; uint32_t rxData; uint32_t header; int32_t freq; uint32_t cnt; uint32_t numRead; fm10k_mem_spi_Enable(mem); ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, ¤tSpiCtrl.value); if (FM10K_SPI_FREQ_KHZ > 0) { freq = ((100000 / (int) FM10K_SPI_FREQ_KHZ) / 2) - 1; if (freq < 0) { freq = 0; } spiCtrl.fields.Freq = freq; } spiCtrl.fields.Enable = currentSpiCtrl.fields.Enable; /* header: command (1 byte: READ_BYTES) + address (3 bytes) */ header = (FM10K_SPI_HEADER_COMMAND_READ_BYTES << 24) | (address & 0xffffff); WriteRegister32(mem, FM10K_REGISTER_SPI_HEADER, header); /* first loop only: set send header flag. Following loops: shift only data. */ spiCtrl.fields.Command |= 0x1; cnt = 0; while (cnt < len) { /* determine the number of data bytes to read [1..4] */ numRead = (len - cnt) > 3 ? 4 : (len - cnt); /* set 'shift data' flag and number of data bytes */ spiCtrl.fields.Command |= 0x4; spiCtrl.fields.DataSize = numRead & 3; if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } /* get data */ ReadRegister32(mem, FM10K_REGISTER_SPI_RX_DATA, &rxData); /* push the read data into the array */ while (numRead) { numRead--; data[cnt++] = (rxData >> (numRead * 8)) & 0xff; } spiCtrl.fields.Command = 0; spiCtrl.fields.DataSize = 0; } /* release CS */ spiCtrl.fields.Command = 0x8; if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } fm10k_mem_spi_Disable(mem); return 0; } uint32_t fm10k_mem_spi_EnableSectorProtection(uintptr_t mem) { SPI_CTRL currentSpiCtrl; SPI_CTRL spiCtrl = {0}; uint32_t header; int32_t freq; fm10k_mem_spi_Enable(mem); ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, ¤tSpiCtrl.value); if (FM10K_SPI_FREQ_KHZ > 0) { freq = ((100000 / (int) FM10K_SPI_FREQ_KHZ) / 2) - 1; if (freq < 0) { freq = 0; } spiCtrl.fields.Freq = freq; } spiCtrl.fields.Enable = 1; header = (0x3D << 24) | (0x2A << 16) | (0x7F) | (0xA9); WriteRegister32(mem, FM10K_REGISTER_SPI_HEADER, header); /* first loop only: set send header flag. Following loops: shift only data. */ spiCtrl.fields.Command |= 0x1; /* send command to the flash */ if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } /* release CS */ spiCtrl.fields.Command = 0x8; if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } fm10k_mem_spi_Disable(mem); return 0; } uint32_t fm10k_mem_spi_DisableSectorProtection(uintptr_t mem) { SPI_CTRL currentSpiCtrl; SPI_CTRL spiCtrl = {0}; uint32_t header; int32_t freq; fm10k_mem_spi_Enable(mem); ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, ¤tSpiCtrl.value); if (FM10K_SPI_FREQ_KHZ > 0) { freq = ((100000 / (int) FM10K_SPI_FREQ_KHZ) / 2) - 1; if (freq < 0) { freq = 0; } spiCtrl.fields.Freq = freq; } spiCtrl.fields.Enable = 1; header = (0x3D << 24) | (0x2A << 16) | (0x7F) | (0x9A); WriteRegister32(mem, FM10K_REGISTER_SPI_HEADER, header); /* first loop only: set send header flag. Following loops: shift only data. */ spiCtrl.fields.Command |= 0x1; /* send command to the flash */ if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } /* release CS */ spiCtrl.fields.Command = 0x8; if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } fm10k_mem_spi_Disable(mem); return 0; } uint32_t fm10k_mem_spi_WriteFlash(uintptr_t mem, uint32_t address, const uint8_t *data, uint32_t len) { SPI_CTRL currentSpiCtrl; SPI_CTRL spiCtrl = {0}; uint32_t txData; uint32_t header; int32_t freq; uint32_t cnt; uint32_t numWrite; fm10k_mem_spi_Enable(mem); ReadRegister32(mem, FM10K_REGISTER_SPI_CTRL, ¤tSpiCtrl.value); if (FM10K_SPI_FREQ_KHZ > 0) { freq = ((100000 / (int) FM10K_SPI_FREQ_KHZ) / 2) - 1; if (freq < 0) { freq = 0; } spiCtrl.fields.Freq = freq; } spiCtrl.fields.Enable = 1; /* header: command (1 byte: READ_MODIFY_WRITE) + address (3 bytes) */ header = (FM10K_SPI_HEADER_COMMAND_READ_MODIFY_WRITE_512 << 24) | (address & 0xffffff); WriteRegister32(mem, FM10K_REGISTER_SPI_HEADER, header); /* first loop only: set send header flag. Following loops: shift only data. */ spiCtrl.fields.Command |= 0x1; cnt = 0; while (cnt < len) { /* determine the number of data bytes to read [1..4] */ numWrite = (len - cnt) > 3 ? 4 : (len - cnt); /* set 'shift data' flag and number of data bytes */ spiCtrl.fields.Command |= 0x4; spiCtrl.fields.DataSize = numWrite & 3; txData = 0; while (numWrite--) { txData = (txData << 8) | data[cnt++]; } /* set data to be written */ WriteRegister32(mem, FM10K_REGISTER_SPI_TX_DATA, txData); /* send command to the flash */ if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } spiCtrl.fields.Command = 0; spiCtrl.fields.DataSize = 0; } /* release CS */ spiCtrl.fields.Command = 0x8; if (fm10k_mem_spi_SetCtrlReg(mem, spiCtrl)) { return 1; } fm10k_mem_spi_Disable(mem); return 0; } const KNOWN_FLASH_DEVICE *getKnownFlashFromManufacturerData(SPI_COMMAND_READ_MANUFACTURER_RESULT data) { uint32_t deviceCount = (sizeof(KNOWN_FLASH_DEVICE_LIST) / sizeof(KNOWN_FLASH_DEVICE)) - 1; for (uint32_t i = 0; i < deviceCount; ++i) { if (KNOWN_FLASH_DEVICE_LIST[i].data.value == data.value) { return &KNOWN_FLASH_DEVICE_LIST[i]; } } return NULL; } uint8_t fm10k_mem_spi_LockTake(uintptr_t mem) { uint32_t maxTries = 64; API_SPI_LOCK_STATE spiLock; do { ReadRegister32(mem, FM10K_API_SPI_LOCK_STATE, &spiLock.value); if (!spiLock.fields.LockTaken) { spiLock.fields.LockTaken = 1; spiLock.fields.LockOwner = 0; //Switch API WriteRegister32(mem, FM10K_API_SPI_LOCK_STATE, spiLock.value); nanosleep(&(struct timespec) {0, 50000}, NULL); //Delay to observe if lock was actually taken by us ReadRegister32(mem, FM10K_API_SPI_LOCK_STATE, &spiLock.value); if (spiLock.fields.LockTaken && spiLock.fields.LockOwner == 0) { printf("Taken SPI Lock\n"); return 0; } } else if (spiLock.fields.LockOwner == 0) { //Are we taking a lock again, with same owner? printf("WARNING: SPI Lock was already taken by us, maybe other process was using it?\n"); return 0; } nanosleep(&(struct timespec) {0, 1000}, NULL); } while (--maxTries); return 1; } void fm10k_mem_spi_LockRelease(uintptr_t mem) { API_SPI_LOCK_STATE spiLock; ReadRegister32(mem, FM10K_API_SPI_LOCK_STATE, &spiLock.value); if (!spiLock.fields.LockTaken) { printf("WARNING: SPI Lock was already released\n"); return; } if (spiLock.fields.LockOwner != 0) { printf("WARNING: SPI Lock Owner unexpected: %u\n", spiLock.fields.LockOwner); return; } spiLock.fields.LockTaken = 0; WriteRegister32(mem, FM10K_API_SPI_LOCK_STATE, spiLock.value); printf("Released SPI Lock\n"); } uint32_t bsm_interruptMask[2] = {0, 0}; void fm10k_mem_bsm_DisableInterrupts(uintptr_t mem) { //Save old registers ReadRegister32(mem, FM10K_REGISTER_INTERRUPT_MASK_BSM(0), bsm_interruptMask); ReadRegister32(mem, FM10K_REGISTER_INTERRUPT_MASK_BSM(1), bsm_interruptMask + 1); WriteRegister32(mem, FM10K_REGISTER_INTERRUPT_MASK_BSM(0), 0xFFFFFFFF); WriteRegister32(mem, FM10K_REGISTER_INTERRUPT_MASK_BSM(1), 0xFFFFFFFF); } void fm10k_mem_bsm_RestoreInterrupts(uintptr_t mem) { WriteRegister32(mem, FM10K_REGISTER_INTERRUPT_MASK_BSM(0), bsm_interruptMask[0]); WriteRegister32(mem, FM10K_REGISTER_INTERRUPT_MASK_BSM(1), bsm_interruptMask[1]); } uint8_t fm10k_mem_TakePlatformLock(uintptr_t mem) { if (fm10k_mem_spi_LockTake(mem)) { return 1; } fm10k_mem_bsm_DisableInterrupts(mem); return 0; } void fm10k_mem_ReleasePlatformLock(uintptr_t mem) { fm10k_mem_spi_LockRelease(mem); fm10k_mem_bsm_RestoreInterrupts(mem); } int fm10k_pci_unmapDevice(FM10K_PCI_MAPPED_DEVICE *map) { if (map->map != NULL && map->map != MAP_FAILED) { if (munmap(map->map, FM10K_BAR4_SIZE) != 0) { return 1; } map->map = NULL; } if (map->fd > 0) { if (close(map->fd) != 0) { return 1; } map->fd = 0; } return 0; } int fm10k_pci_mapDevice(const FM10K_PCI_DEVICE *device, FM10K_PCI_MAPPED_DEVICE *map) { int fdBAR4; fdBAR4 = open(device->bar4Path, O_RDWR); if (fdBAR4 <= 0) { printf("Unable to open BAR4 resource %s to read NVM\n", device->bar4Path); return 1; } void *memmapAddr; memmapAddr = mmap(NULL, FM10K_BAR4_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fdBAR4, 0); if (memmapAddr == MAP_FAILED) { printf("Unable to map BAR4 resource %s to read NVM\n", device->bar4Path); close(fdBAR4); return 1; } map->device = device; map->fd = fdBAR4; map->map = memmapAddr; if (fm10k_mem_TakePlatformLock((uintptr_t) map->map)) { //Error taking lock fm10k_pci_unmapDevice(map); return 1; } return 0; } int fm10k_pci_findDevice(const FM10K_PCI_DEVICES *devices, FM10K_PCI_MAPPED_DEVICE *map) { if (devices->count == 0) { return 1; } for (uint32_t i = 0; i < devices->count; ++i) { if (fm10k_pci_mapDevice(&devices->devices[i], map) == 0) { return 0; } } return 1; } FM10K_PCI_DEVICES fm10k_pci_findDevices() { DIR *folder; char fileName[PATH_MAX]; FM10K_PCI_DEVICES devices; devices.count = 0; folder = opendir("/sys/bus/pci/devices"); if (folder == NULL) { return devices; } struct dirent *entry; while ((entry = readdir(folder))) { if (entry->d_type == DT_DIR || entry->d_type == DT_LNK) { sprintf(fileName, "/sys/bus/pci/devices/%s/vendor", entry->d_name); FILE *vendorBytesPointer = fopen(fileName, "rb"); if (vendorBytesPointer != NULL) { char *vendorBytesBuffer = NULL; size_t vendorBytesLen; size_t vendorBytesRead = getdelim(&vendorBytesBuffer, &vendorBytesLen, '\0', vendorBytesPointer); if (vendorBytesRead != -1) { unsigned int vendor = (unsigned int) strtoul(vendorBytesBuffer, NULL, 0); if (vendor == 0x8086) { //Intel Corporation sprintf(fileName, "/sys/bus/pci/devices/%s/device", entry->d_name); FILE *classBytesPointer = fopen(fileName, "rb"); if (classBytesPointer != NULL) { char *classBytesBuffer = NULL; size_t classBytesLen; size_t classBytesRead = getdelim(&classBytesBuffer, &classBytesLen, '\0', classBytesPointer); if (classBytesRead != -1) { unsigned int class = (unsigned int) strtoul(classBytesBuffer, NULL, 0); if (class == 0x15a4 || class == 0x15d0 || class == 0x15d5) { //FM10000 devices sprintf(fileName, "/sys/bus/pci/devices/%s/resource4", entry->d_name); if (access(fileName, F_OK) == 0) { devices.devices[devices.count].vendor = vendor; devices.devices[devices.count].class = class; sprintf(devices.devices[devices.count].bar4Path, "/sys/bus/pci/devices/%s/resource4", entry->d_name); ++devices.count; } } } if (classBytesBuffer != NULL) { free(classBytesBuffer); } fclose(classBytesPointer); } } } if (vendorBytesBuffer != NULL) { free(vendorBytesBuffer); } fclose(vendorBytesPointer); } if (devices.count >= FM10K_PCI_DEVICE_MAX) { break; } } } closedir(folder); return devices; }