/********************************************************************** * COPYRIGHT 2020 MULTI-TECH SYSTEMS, INC. * * ALL RIGHTS RESERVED BY AND FOR THE EXCLUSIVE BENEFIT OF * MULTI-TECH SYSTEMS, INC. * * MULTI-TECH SYSTEMS, INC. - CONFIDENTIAL AND PROPRIETARY * INFORMATION AND/OR TRADE SECRET. * * NOTICE: ALL CODE, PROGRAM, INFORMATION, SCRIPT, INSTRUCTION, * DATA, AND COMMENT HEREIN IS AND SHALL REMAIN THE CONFIDENTIAL * INFORMATION AND PROPERTY OF MULTI-TECH SYSTEMS, INC. * USE AND DISCLOSURE THEREOF, EXCEPT AS STRICTLY AUTHORIZED IN A * WRITTEN AGREEMENT SIGNED BY MULTI-TECH SYSTEMS, INC. IS PROHIBITED. * ***********************************************************************/ #include "Fpga.h" /* -------------------------------------------------------------------------- */ /* --- Flash opcodes ---------------------------------------------------------*/ #define WR_STATUS_REG 0x01 /* Write Status Register */ #define PAGE_PROGRAM 0x02 /* Write up to a Page of the Memory */ #define READ_DATA 0x03 /* Read from the Memory */ #define WRITE_DISABLE 0x04 /* Disable Writing to the Memory */ #define RD_STATUS_REG_1 0x05 /* Read Status Register-1 */ #define WRITE_ENABLE 0x06 /* Enable Writing to the Memory */ #define FAST_READ_DATA 0x0B /* Fast Read from the Memory */ #define SECTOR_ERASE 0x20 /* Erase a Sector (4kb) */ u #define RD_STATUS_REG_2 0x35 /* Read Status Register-2 */ #define UNIQUE_ID 0x4B /* Read Unique ID */ #define WE_STATUS_REG 0x50 /* Write Enable for Status Registers */ #define BLOCK_ERASE_32 0x52 /* Erase a Block (32kb) */ #define CHIP_ERASE 0x60 /* Erase Entire Chip */ #define MNFTR_DEV_ID 0x90 /* Read Manufacturer ID followed by Device ID */ #define JEDEC_ID 0x9F /* Read JEDEC ID */ #define CHIP_RELEASE 0xAB /* Release chip from power down */ #define BLOCK_ERASE_64 0xD8 /* Erase a Block (64kb) */ static const uint8_t fpga_version[] = {28, 31, 33, 35, 37}; static const struct mtac_reg_s loregs[3] = { {-1, 0, 0, 0, 2, 0, 0}, /* PAGE_REG */ {-1, 0, 7, 0, 1, 0, 0}, /* SOFT_RESET */ {-1, 1, 0, 0, 8, 1, 103} /* VERSION */ }; /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ static const char *valid_hashes[3][4] = { { "d9f811fcab57947db3c2323242885a32a7f095a069d3386a148466e7f3da53" "53", /* mtcdt v28*/ "903c1199df46d38683b1aa9fc88310abe2f317c01c3aefa77987990874aba4" "20", /* mtcdt v31*/ "7c190506b969aea6198daffb6c9b47685f3a4dc3ce18565c66542bac27d6f2" "4e", /* mtcdt v33*/ "72bcdfda72bf8677d585330caa3d609615d08d4ca6d7951f0ebbcb5a93306b" "3c" /* mtcdt v35*/ }, { "54e41b186b2c91f1bcf249648c50357165d361101fc4fe20ee9b8f0c40dce2" "5d" /* mtcdt3 v35*/ }, { "07317fe9ca59393c074215c9d923d8d01025654883291a5e89b27d21668e22" "63", /* mtcap v28*/ "f208ef5cae03e703951bb8799172a5eaadb74ddb90bf3e65c32030c008a88e" "75", /* mtcap v31*/ "aaecd468b187703dbbf76022b00268dba2a5f25300da6486d420f476c83638" "5c", /* mtcap v33*/ "876cc5683f612c09f96bacb27fff170358c90f3bd76a5c61ec41504eabba83" "13" /* mtcap v35*/ }, }; /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DEFINITION ------------------------------------------ */ /* hash outputBuffer */ void Mtac15Fpga::sha256_hash_string(unsigned char hash[SHA256_DIGEST_LENGTH], char outputBuffer[65]) { int i = 0; for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(outputBuffer + (i * 2), "%02x", (unsigned char)hash[i]); } outputBuffer[64] = 0; printInfo("OutputBuffer hash: %s", outputBuffer); } /* Initialize, update and finalize sha256 */ void Mtac15Fpga::sha256(char *string, char outputBuffer[65]) { unsigned char hash[SHA256_DIGEST_LENGTH]; int len; SHA256_CTX sha256; SHA256_Init(&sha256); len = strlen(string); SHA256_Update(&sha256, string, len); SHA256_Final(hash, &sha256); int i = 0; for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(outputBuffer + (i * 2), "%02x", (unsigned char)hash[i]); } outputBuffer[64] = 0; printInfo("OutputBuffer finalized: %s", outputBuffer); } /* Open input file and verify sha256 with verified list */ int Mtac15Fpga::sha256_file(const char *path) { printInfo("Checking hash on input file: %s", path); FILE *file = fopen(path, "rb"); if (!file) { printError("File %s not found", path); return -1; } else { printInfo("Checking file %s", path); } unsigned int i; char file_hash[65]; unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_CTX sha256; SHA256_Init(&sha256); const int bufSize = 32768; unsigned char *buffer = (unsigned char *)malloc(bufSize); int bytesRead = 0; if (!buffer) return ENOMEM; while ((bytesRead = fread(buffer, 1, bufSize, file))) { SHA256_Update(&sha256, buffer, bytesRead); } SHA256_Final(hash, &sha256); sha256_hash_string(hash, file_hash); fclose(file); free(buffer); printInfo("Calculated input file hash: %s", file_hash); for (i = 0; i < sizeof(valid_hashes[hardwareType]) / sizeof(valid_hashes[hardwareType][0]); ++i) { if (!strcmp(valid_hashes[hardwareType][i], file_hash)) { printInfo("File verified"); return 0; } } printError("Invalid input file"); return -1; } int Mtac15Fpga::spiOpen() { int *spi_device = NULL; int dev; int a = 0, b = 0; int i; /* allocate memory for the device descriptor */ spi_device = (int *)malloc(sizeof(int)); if (spi_device == NULL) { printError("Malloc failed"); return -1; } /* open SPI device */ dev = open(spiPath.c_str(), O_RDWR); if (dev < 0) { printError("Failed to open SPI device %s", spiPath.c_str()); return -1; } /* setting SPI mode to 'mode 0' */ i = SPI_MODE_3; a = ioctl(dev, SPI_IOC_WR_MODE, &i); b = ioctl(dev, SPI_IOC_RD_MODE, &i); if ((a < 0) || (b < 0)) { printError("SPI port failed to set IOC MODE 0"); close(dev); free(spi_device); return -1; } /* setting SPI max clk (in Hz) */ i = SPI_SPEED; a = ioctl(dev, SPI_IOC_WR_MAX_SPEED_HZ, &i); b = ioctl(dev, SPI_IOC_RD_MAX_SPEED_HZ, &i); if ((a < 0) || (b < 0)) { printError("SPI port failed to set MAX SPEED"); close(dev); free(spi_device); return -1; } /* setting SPI to MSB first */ i = 0; a = ioctl(dev, SPI_IOC_WR_LSB_FIRST, &i); b = ioctl(dev, SPI_IOC_RD_LSB_FIRST, &i); if ((a < 0) || (b < 0)) { printError("SPI port failed to set MSB FIRST"); close(dev); free(spi_device); return -1; } /* setting SPI to 8 bits per word */ i = 0; a = ioctl(dev, SPI_IOC_WR_BITS_PER_WORD, &i); b = ioctl(dev, SPI_IOC_RD_BITS_PER_WORD, &i); if ((a < 0) || (b < 0)) { printError("SPI port failed to set 8 bits-per-word"); close(dev); return -1; } *spi_device = dev; spi_target_ptr = (void *)spi_device; return 0; } int Mtac15Fpga::spiRead(uint8_t spi_mux_target, uint8_t address, uint8_t *data) { int spi_device; uint8_t out_buf[3]; uint8_t in_buf[ARRAY_SIZE(out_buf)]; uint8_t command_size; struct spi_ioc_transfer k; int a; spi_device = *(int *)spi_target_ptr; /* spi_target cannot be null beforehand */ /* prepare frame to be sent */ out_buf[0] = spi_mux_target; out_buf[1] = READ_ACCESS | (address & 0x7F); out_buf[2] = 0x00; command_size = 3; /* I/O transaction */ memset(&k, 0, sizeof(k)); /* clear k */ k.tx_buf = (unsigned long)out_buf; k.rx_buf = (unsigned long)in_buf; k.len = command_size; k.cs_change = 1; a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); /* determine return code */ if (a != (int)k.len) { printError("SPI read failure"); return -1; } else { *data = in_buf[command_size - 1]; return 0; } } /* Simple spi write to fpga*/ int Mtac15Fpga::spiWrite(uint8_t spi_mux_target, uint8_t address, uint8_t data) { int spi_device; uint8_t out_buf[3]; uint8_t command_size; struct spi_ioc_transfer k; int a; spi_device = *(int *)spi_target_ptr; /* spi_target cannot be null beforehand */ /* prepare frame to be sent */ out_buf[0] = spi_mux_target; out_buf[1] = WRITE_ACCESS | (address & 0x7F); out_buf[2] = data; command_size = 3; /* I/O transaction */ memset(&k, 0, sizeof(k)); /* clear k */ k.tx_buf = (unsigned long)out_buf; k.len = command_size; k.speed_hz = SPI_SPEED; k.cs_change = 1; k.bits_per_word = 8; a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); /* determine return code */ if (a != (int)k.len) { printError("SPI write failure"); return -1; } else { printDebug("SPI write success"); return 0; } } int Mtac15Fpga::spiClose() { int spi_device; int a; /* close file & deallocate file descriptor */ spi_device = *(int *)spi_target_ptr; /* check that spi_target is not null */ a = close(spi_device); free(spi_target_ptr); /* determine return code */ if (a < 0) { printError("SPI port failed to close"); return -1; } else { return 0; } } /* write to creset pin */ int Mtac15Fpga::cresetWrite(char num) { std::string cresetPath = SYSFS_PLATFORM + port + CRESET; int fd = open(cresetPath.c_str(), O_WRONLY); if (fd < 0) { printError("Unable to lock creset file: %d %s", fd, cresetPath.c_str()); return -1; } write(fd, &num, 1); close(fd); return 0; } /* Release Power-down instruction releases the device from said state allowing device communication */ int Mtac15Fpga::releaseDevice() { int mtac_ret; int spi_device; struct spi_ioc_transfer k; int ret; size_t command_size = 5; char out_buf[command_size]; char in_buf[command_size]; /* prepare frame to be sent */ out_buf[0] = CHIP_RELEASE; out_buf[1] = 0; out_buf[2] = 0; out_buf[3] = 0; out_buf[4] = 0; /* I/O transaction */ mtac_ret = spiOpen(); spi_device = *(int *)spi_target_ptr; /* spi_target cannot be null beforehand */ memset(&k, 0, sizeof(k)); /* clear k */ k.tx_buf = (unsigned long)out_buf; k.rx_buf = (unsigned long)in_buf; k.len = command_size; k.speed_hz = SPI_SPEED; k.cs_change = 1; k.bits_per_word = 8; ret = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); mtac_ret = spiClose(); /* determine return code */ if (ret != (int)k.len) { printError("Release failed"); mtac_ret = -1; } else { usleep(1000); mtac_ret = 0; } return mtac_ret; } /* Write enable instruction sets the write enable latch in the status register to 1. It needs to be set before a page can be programmed or chip erased */ int Mtac15Fpga::writeEnable() { int mtac_ret; int spi_device; struct spi_ioc_transfer k; int ret; size_t command_size = 1; char out_buf[command_size]; char in_buf[command_size]; /* prepare frame to be sent */ out_buf[0] = 0x06; /* I/O transaction */ mtac_ret = spiOpen(); spi_device = *(int *)spi_target_ptr; /* spi_target cannot be null beforehand */ memset(&k, 0, sizeof(k)); /* clear k */ k.tx_buf = (unsigned long)out_buf; k.rx_buf = (unsigned long)in_buf; k.len = command_size; k.speed_hz = SPI_SPEED; k.cs_change = 0; k.bits_per_word = 8; ret = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); mtac_ret = spiClose(); /* determine return code */ if (ret != (int)k.len) { printError("Write Enable failed"); mtac_ret = -1; } else { usleep(1000); printDebug("Write Enable successful"); mtac_ret = 0; } return mtac_ret; } /* Verify that the chip erase was successful */ int Mtac15Fpga::chipEraseVerify() { int mtac_ret; int spi_device; struct spi_ioc_transfer k; int a, ret; uint16_t command_size = 256 + 5; char out_buf[command_size]; char in_buf[command_size]; /* prepare frame to be sent */ out_buf[0] = 0x0B; out_buf[1] = 0x00; out_buf[2] = 0x00; out_buf[3] = 0x00; out_buf[4] = 0x00; /* I/O transaction */ mtac_ret = spiOpen(); spi_device = *(int *)spi_target_ptr; /* spi_target cannot be null beforehand */ memset(&k, 0, sizeof(k)); /* clear k */ k.tx_buf = (unsigned long)out_buf; k.rx_buf = (unsigned long)in_buf; k.len = command_size; k.speed_hz = SPI_SPEED; k.cs_change = 1; k.bits_per_word = 8; ret = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); /* determine return code */ if (ret != (int)k.len) { printError("Chip transfer failed"); mtac_ret = -1; } else { printDebug("Chip transfer successful"); mtac_ret = 0; } mtac_ret = spiClose(); /* verify that the chip was erased */ for (a = 5; a < command_size; a++) { if (in_buf[a] != 0xFF) { mtac_ret = spiClose(); return mtac_ret; } } return mtac_ret; } /* Chip erase instruction sets all memory within the device to the erased state of all 1s (FFh) */ int Mtac15Fpga::chipErase() { int mtac_ret; int spi_device; struct spi_ioc_transfer k; int ret; size_t command_size = 1; char out_buf[command_size]; char in_buf[command_size]; /* prepare frame to be sent */ out_buf[0] = CHIP_ERASE; /* I/O transaction */ mtac_ret = spiOpen(); spi_device = *(int *)spi_target_ptr; /* spi_target cannot be null beforehand */ memset(&k, 0, sizeof(k)); /* clear k */ k.tx_buf = (unsigned long)out_buf; k.rx_buf = (unsigned long)in_buf; k.len = command_size; k.speed_hz = SPI_SPEED; k.cs_change = 0; k.bits_per_word = 8; ret = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); sleep(5); mtac_ret = spiClose(); /* determine return code */ if (ret != (int)k.len) { printError("Chip Erase transfer failed"); mtac_ret = -1; } else { printDebug("Chip Erase transfer successful"); chipEraseVerify(); mtac_ret = 0; } return mtac_ret; } /* erase entire flash */ int Mtac15Fpga::mtacErase() { int ret; printInfo("Erasing flash"); /* pull device out of powerdown state */ ret = releaseDevice(); if (ret != 0) { return ret; } /* enable writing to flash */ ret = writeEnable(); if (ret != 0) { return ret; } /* send chip erase command to flash */ ret = chipErase(); if (ret != 0) { return ret; } /* pull device out of powerdown state */ ret = releaseDevice(); if (ret != 0) { return ret; } return 0; } /* Page Program instruction allows writing upto 256 bytes (a page) of data to be programmed at previously erased memory locations Write enable must be issued first */ int Mtac15Fpga::pageProgram(uint8_t adr_lower, uint8_t adr_higher, uint32_t data[256]) { int mtac_ret; int spi_device; struct spi_ioc_transfer k; int a, h; size_t command_size = 260; char out_buf[command_size]; char in_buf[command_size]; /* prepare frame to be sent */ out_buf[0] = PAGE_PROGRAM; out_buf[1] = adr_higher; out_buf[2] = adr_lower; out_buf[3] = 0x00; for (h = 0; h < 256; h++) { out_buf[h + 4] = data[h]; } /* I/O transaction */ mtac_ret = spiOpen(); spi_device = *(int *)spi_target_ptr; /* spi_target cannot be null beforehand */ memset(&k, 0, sizeof(k)); /* clear k */ k.tx_buf = (unsigned long)out_buf; k.rx_buf = (unsigned long)in_buf; k.len = command_size; k.speed_hz = SPI_SPEED; k.cs_change = 1; k.bits_per_word = 8; a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); printDebug("Writing Page %x%x to MTAC\r", adr_higher, adr_lower); usleep(10000); mtac_ret = spiClose(); /* determine return code */ if (a != (int)k.len) { printError("SPI write failure"); mtac_ret = -1; } else { printDebug("SPI write success"); mtac_ret = 0; } return mtac_ret; } /* write to mtac card with input file */ int Mtac15Fpga::mtacProgram(const char input_file[]) { FILE *f; struct stat st; size_t file_size; int ret = 0; uint32_t i = 0, index, result, offset, page_address, no_pages, page_data[256]; uint8_t adr_lower, adr_higher; /* get data array from file to be writen */ f = fopen(input_file, "r"); stat(input_file, &st); file_size = st.st_size; uint8_t *p_array; p_array = (uint8_t *)malloc(sizeof(uint8_t) * file_size); unsigned int data[file_size]; while ((result = fscanf(f, "%x ", data + i)) == 1) { i++; } fclose(f); no_pages = ceil(i / 256.0); /* program one page at a time */ for (page_address = 0x00; page_address < no_pages; page_address++) { /* mask page address */ adr_higher = (page_address >> 8) & 0xff; adr_lower = page_address & 0xff; /* calculate initial data offset */ offset = page_address * 256; /* enable writing to flash */ ret = writeEnable(); if (ret != 0) { free(p_array); break; } /* assign data for page to be written */ for (index = 0; index < 256; index++) { page_data[index] = index + offset > i ? 0xff : data[index + offset]; } /* program single page*/ ret = pageProgram(adr_lower, adr_higher, page_data); if (ret != 0) { free(p_array); break; } /* release device from page program powerdown */ ret = releaseDevice(); if (ret != 0) { free(p_array); break; } } free(p_array); return ret; } /* Make sure no device is using the spidev bus to prevent contention*/ int Mtac15Fpga::busContention() { char number[1024]; FILE *f = popen("lsof", "r"); while (fgets(number, 1024, f) != NULL) { if (strstr(number, "spidev") != NULL) { printError("The accessory card is being used by another process: " "\n %sPlease " "close all LoRaWAN processes and try again", number); pclose(f); return -1; } } pclose(f); return 0; } /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ /* Constructor used fpga upgrade utility */ Mtac15Fpga::Mtac15Fpga(std::string inputFile, std::string forcedPath) { FILE *fp = fopen(DEVICE_INFO_FILE, "r"); char buf[0XFFFF]; if (fp== NULL) { perror("Unable to open device info file"); exitHandler(errno); } rapidjson::FileReadStream input(fp, buf, sizeof(buf)); deviceInfo.ParseStream(input); fclose(fp); if (!deviceInfo.HasMember("hardwareVersion")) { printError("%s does not have hardware version info, exiting", DEVICE_INFO_FILE); exitHandler(99); } std::string hwVersion = deviceInfo["hardwareVersion"].GetString(); if (deviceInfo.HasMember("accessoryCards") && deviceInfo["accessoryCards"].IsArray() && deviceInfo["accessoryCards"].Size() > 0) { rapidjson::SizeType acCardCount = deviceInfo["accessoryCards"].Size(); if (hwVersion.find("MTCDT3") != std::string::npos) { hardwareType = HARDWARE_MTCDT3; } else if (hwVersion.find("MTCDT") != std::string::npos) { hardwareType = HARDWARE_MTCDT; } else if (hwVersion.find("MTCAP") != std::string::npos) { hardwareType = HARDWARE_MTCAP; } else { return; } if (inputFile.empty()) { input_file = MTCDT_DEFAULT_FILE; } else { input_file = inputFile; } if(forcedPath.empty()) { port = deviceInfo["accessoryCards"][0]["port"].GetString(); if (port.back() == '2') { spiPath = LORA_1_5_MTCDT_SPI_AP_2; } else { spiPath = LORA_1_5_MTCDT_SPI_AP_1; } } else { if (forcedPath.compare("1") == 0) { spiPath = LORA_1_5_MTCDT_SPI_AP_1; port = "ap1"; } else if (forcedPath.compare("2") == 0) { port = "ap2"; spiPath = LORA_1_5_MTCDT_SPI_AP_2; } else { spiPath = forcedPath; rapidjson::SizeType i; bool found = false; for (i = 0; i < acCardCount; i++) { if (spiPath.compare(deviceInfo["accessoryCards"][i]["spiPath"].GetString()) == 0) { port = deviceInfo["accessoryCards"][i]["port"].GetString(); found = true; break; } } if (!found) { printError("Invalid spi path: %s", spiPath.c_str()); exitHandler(99); } } } /* Sanity check config options with device info.json */ bool valid_config = false; rapidjson::SizeType j; for (j = 0; j < acCardCount; j++) { if (spiPath.compare(deviceInfo["accessoryCards"][j]["spiPath"].GetString()) == 0 && port.compare(deviceInfo["accessoryCards"][j]["port"].GetString()) == 0) { valid_config = true; break; } } if (!valid_config) { printError("Path %s with port %s does not in exist in %s. Please set a valid config", spiPath.c_str(), port.c_str(), DEVICE_INFO_FILE); exitHandler(99); } getFpgaVersion(); } else if (hwVersion.find("MTCAP") != std::string::npos) { hardwareType = HARDWARE_MTCAP; if (inputFile.empty()) { input_file = MTCAP_DEFAULT_FILE; } else { input_file = inputFile; } spiPath = "/dev/spidev0.0"; getFpgaVersion(); } else { printError("No accessory cards installed/invalid hardware"); exitHandler(99); } } void Mtac15Fpga::printFpgaVersion() { if(fpgaVersion == 255 || fpgaVersion == 0) { printError("Found invalid FPGA Version %d on spi path %s", fpgaVersion, spiPath.c_str()); exitHandler(errno); } else { printInfo("Found FPGA Version %d on spi path %s", fpgaVersion, spiPath.c_str()); exit(0); } } /* Constructor used by device_info.json generator */ Mtac15Fpga::Mtac15Fpga(const std::string path) { spiPath = path; } int Mtac15Fpga::FpgaVersion() { return fpgaVersion; } /* Open spi device and get fpga version from register */ int Mtac15Fpga::getFpgaVersion() { int ret = spiOpen(); if (ret != 0) { printError("Could not open SPI port %s", spiPath.c_str()); } else { /* detect if the gateway has an FPGA with SPI mux header support */ ret = spiRead(MTAC_FPGA, loregs[MTAC_VERSION].addr, &fpgaVersion); if (ret != 0) { printError("Could not read FPGA version"); } spiClose(); } spi_target_ptr = NULL; return ret; } /* setup and upgrade the mtac card with the file specified */ int Mtac15Fpga::upgradeFpga() { int ret; if (input_file.empty()) { printError("Invalid input file %s", input_file.c_str()); return -1; } /* check that no other device is using the bus */ ret = busContention(); if (ret != 0) { return ret; } /* check input file checksum */ ret = sha256_file(input_file.c_str()); if (ret != 0) { return ret; } /* pull creset down to access spi flash */ ret = cresetWrite('0'); if (ret != 0) { return ret; } sleep(1); /* erase chip before flashing new firmware */ ret = mtacErase(); if (ret != 0) { return ret; } /* program user specified firmware */ printInfo("Programming flash"); ret = mtacProgram(input_file.c_str()); if (ret != 0) { return ret; } else { printInfo("Write Complete. Resetting FPGA"); } /* pull creset up to access FPGA */ ret = cresetWrite('1'); if (ret != 0) { return ret; } sleep(5); printInfo("Reading New FPGA configuration"); ret = getFpgaVersion(); if (ret != 0) { return ret; } printInfo("New FPGA version: %d", fpgaVersion); return 0; }