/* ================================================================== >>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< ------------------------------------------------------------------ Copyright (c) 2019-2024 by Lattice Semiconductor Corporation ALL RIGHTS RESERVED ------------------------------------------------------------------ DISCLAIMER: LATTICE MAKES NO WARRANTIES ON THIS FILE OR ITS CONTENTS, WHETHER EXPRESSED, IMPLIED, STATUTORY, OR IN ANY PROVISION OF THE LATTICE PROPEL LICENSE AGREEMENT OR COMMUNICATION WITH LICENSEE, AND LATTICE SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. LATTICE DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED HEREIN WILL MEET LICENSEE 'S REQUIREMENTS, OR THAT LICENSEE' S OPERATION OF ANY DEVICE, SOFTWARE OR SYSTEM USING THIS FILE OR ITS CONTENTS WILL BE UNINTERRUPTED OR ERROR FREE, OR THAT DEFECTS HEREIN WILL BE CORRECTED. LICENSEE ASSUMES RESPONSIBILITY FOR SELECTION OF MATERIALS TO ACHIEVE ITS INTENDED RESULTS, AND FOR THE PROPER INSTALLATION, USE, AND RESULTS OBTAINED THEREFROM. LICENSEE ASSUMES THE ENTIRE RISK OF THE FILE AND ITS CONTENTS PROVING DEFECTIVE OR FAILING TO PERFORM PROPERLY AND IN SUCH EVENT, LICENSEE SHALL ASSUME THE ENTIRE COST AND RISK OF ANY REPAIR, SERVICE, CORRECTION, OR ANY OTHER LIABILITIES OR DAMAGES CAUSED BY OR ASSOCIATED WITH THE SOFTWARE.IN NO EVENT SHALL LATTICE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS FILE OR ITS CONTENTS, EVEN IF LATTICE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LATTICE 'S SOLE LIABILITY, AND LICENSEE' S SOLE REMEDY, IS SET FORTH ABOVE. LATTICE DOES NOT WARRANT OR REPRESENT THAT THIS FILE, ITS CONTENTS OR USE THEREOF DOES NOT INFRINGE ON THIRD PARTIES' INTELLECTUAL PROPERTY RIGHTS, INCLUDING ANY PATENT. IT IS THE USER' S RESPONSIBILITY TO VERIFY THE USER SOFTWARE DESIGN FOR CONSISTENCY AND FUNCTIONALITY THROUGH THE USE OF FORMAL SOFTWARE VALIDATION METHODS. ------------------------------------------------------------------ ================================================================== */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #include "PCIEDMA_IF.h" const char *PCIEDMA_IF::PCIE_SC_BRD = "LSC"; const char *PCIEDMA_IF::PCIEDMA_DEMO = "DMA"; #define NO_SUCH_GUID -1 #define NO_SUCH_BOARD_ID -2 #define NO_SUCH_DEMO_ID -3 #define NO_MORE_BOARDS -4 #define PARAM_ERROR -5 #define OPEN_FILE_FAILED -6 #define MaxBlockSize 256 /** * Create an interface to a Lattice PCIe Driver and eval board. * @param pBoardID the PCI Vendor and Device register pair indicating the board to open * @param pDemoID the PCI SubSystem regsiter value indicating the Demo IP to open * @param devNum the instance of the board (i.e. locate and open the 2nd SC80 x4 board) * @param pFriendlyName (NOT USED) text string naming the board, set in registry, * @param pFunctionName text string naming a specific driver function to open (i.e. use DMA chan #1) */ PCIEDMA_IF::PCIEDMA_IF(const char *pBoardID, const char *pDemoID, uint32_t devNum, const char *pFriendlyName, const char *pFunctionName) { int ret = 0; hDev = -1; if ((pBoardID == NULL) || (pDemoID == NULL)) return; cout<<"BoardID: "<> 28; minor = (rw.value & 0x0FF00000) >> 20; minor_update = (rw.value & 0x000F0000) >> 16; lsc_internal = rw.value & 0x0000FFFF ; sprintf(buf, "%01x.%02x.%01x.%04x\n", major, minor, minor_update, lsc_internal); ostr.append("Lattice PCIe Dma IP v"); ostr.append(buf); return(true); } /** * Generates F2H descriptors using the PCIE DMA interface. * * This function calls the IOCTL_PCIEDMA_F2H_DESC_GEN ioctl to perform the descriptor generation. * * @param arg (struct dma_ubuf_ioctl_arg*): A pointer to the dma_ubuf_ioctl_arg structure. * @return int: 0 on success. * @throws PCIEDMA_IF_Error: If F2H descriptor generation fails. */ int PCIEDMA_IF::PcieDmaF2hDescGen(struct dma_f2h_ubuf_ioctl_arg *arg) { if(ioctl(hDev, IOCTL_PCIEDMA_F2H_DESC_GEN, arg) < 0) throw(PCIEDMA_IF_Error("PCIEDMA_IF: F2H Descriptor Generation Failed! \n")); return 0; } /** * Generates H2F descriptors using the PCIE DMA interface. * * This function calls the IOCTL_PCIEDMA_H2F_DESC_GEN ioctl to perform the descriptor generation. * * @param arg (struct dma_ubuf_ioctl_arg*): A pointer to the dma_ubuf_ioctl_arg structure. * @return int: 0 on success. * @throws PCIEDMA_IF_Error: If H2F descriptor generation fails. */ int PCIEDMA_IF::PcieDmaH2fDescGen(struct dma_h2f_ubuf_ioctl_arg *arg) { if(ioctl(hDev, IOCTL_PCIEDMA_H2F_DESC_GEN, arg) < 0) throw(PCIEDMA_IF_Error("PCIEDMA_IF: H2F Descriptor Generation Failed! \n")); return 0; } /** * Starts or stops F2H DMA using the PCIE DMA interface. * * This function calls the IOCTL_PCIEDMA_F2H_START or IOCTL_PCIEDMA_F2H_STOP ioctl * based on the value of the 'on' parameter. * * @param on (uint8_t): If 1, starts F2H DMA; otherwise, stops F2H DMA. * @return int: 0 on success. * @throws PCIEDMA_IF_Error: If F2H DMA start or stop fails. */ int PCIEDMA_IF::PcieDmaF2hStartCtrl(uint8_t on) { if (on == 1) // start { if(ioctl(hDev, IOCTL_PCIEDMA_F2H_START) < 0) throw(PCIEDMA_IF_Error("PCIEDMA_IF: F2H DMA Start Failed! \n")); } else // stop { if(ioctl(hDev, IOCTL_PCIEDMA_F2H_STOP) < 0) throw(PCIEDMA_IF_Error("PCIEDMA_IF: F2H DMA Stop Failed! \n")); } return 0; } /** * Starts or stops H2F DMA using the PCIE DMA interface. * * This function calls the IOCTL_PCIEDMA_H2F_START or IOCTL_PCIEDMA_H2F_STOP ioctl * based on the value of the 'on' parameter. * * @param on (uint8_t): If 1, starts H2F DMA; otherwise, stops H2F DMA. * @return int: 0 on success. * @throws PCIEDMA_IF_Error: If H2F DMA start or stop fails. */ int PCIEDMA_IF::PcieDmaH2fStartCtrl(uint8_t on) { if (on == 1) // start { if(ioctl(hDev, IOCTL_PCIEDMA_H2F_START) < 0) throw(PCIEDMA_IF_Error("PCIEDMA_IF: H2F DMA Start Failed! \n")); } else // stop { if(ioctl(hDev, IOCTL_PCIEDMA_H2F_STOP) < 0) throw(PCIEDMA_IF_Error("PCIEDMA_IF: H2F DMA Stop Failed! \n")); } return 0; } /** * Checks the completion status of F2H DMA using the PCIE DMA interface. * * This function calls the IOCTL_PCIEDMA_F2H_CHECK_DONE ioctl with the specified timeout. * If the ioctl fails, the error status is returned; otherwise, the current DMA status is updated. * * @param cur_dma_sts (uint32_t*): Pointer to store the current DMA status. * @param timeout_ms (uint32_t): Timeout in milliseconds for checking completion. * @return int: 0 on success, or an error code if the ioctl fails. */ int PCIEDMA_IF::PcieDmaF2hCheckDone(uint32_t *cur_dma_sts, uint32_t timeout_ms) { int status = 0; if(ioctl(hDev, IOCTL_PCIEDMA_F2H_CHECK_DONE, &timeout_ms) < 0) status = (errno); else *cur_dma_sts = timeout_ms; return status; } /** * Checks the completion status of H2F DMA using the PCIE DMA interface. * * This function calls the IOCTL_PCIEDMA_H2F_CHECK_DONE ioctl with the specified timeout. * If the ioctl fails, the error status is returned; otherwise, the current DMA status is updated. * * @param cur_dma_sts (uint32_t*): Pointer to store the current DMA status. * @param timeout_ms (uint32_t): Timeout in milliseconds for checking completion. * @return int: 0 on success, or an error code if the ioctl fails. */ int PCIEDMA_IF::PcieDmaH2fCheckDone(uint32_t *cur_dma_sts, uint32_t timeout_ms) { int status = 0; if(ioctl(hDev, IOCTL_PCIEDMA_H2F_CHECK_DONE, &timeout_ms) < 0) status = (errno); else *cur_dma_sts = timeout_ms; return status; } /** * @brief Retrieves the total number of F2H (FPGA to Host) DMA entries. * * This function uses an ioctl call to get the total number of F2H DMA entries. * * @return Total number of F2H DMA entries. * @throws PCIEDMA_IF_Error if the ioctl call fails. */ int PCIEDMA_IF::PcieDmaF2hEntriesRet() { uint32_t val; dma_f2h_entries_ioctl_t entry; entry.f2h_total_entry_ret = 0; if(ioctl(hDev, IOCTL_PCIEDMA_TOTAL_F2H_ENTRIES_CHECK, &entry) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: PcieDmaF2hEntriesRet failed! \n")); } val = (uint32_t)entry.f2h_total_entry_ret; return val; } /** * @brief Retrieves the total number of H2F (Host to FPGA) DMA entries. * This function uses an ioctl call to get the total number of H2F DMA entries. * * @return Total number of H2F DMA entries. * @throws PCIEDMA_IF_Error if the ioctl call fails. */ int PCIEDMA_IF::PcieDmaH2fEntriesRet() { uint32_t val; dma_h2f_entries_ioctl_t entry; entry.h2f_total_entry_ret = 0; if(ioctl(hDev, IOCTL_PCIEDMA_TOTAL_H2H_ENTRIES_CHECK, &entry) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: PcieDmaH2fEntriesRet failed! \n")); } val = (uint32_t)entry.h2f_total_entry_ret; return val; } /** * Read 8 bits from an LSC FPGA register via PCIe bus. * @param offset BAR + address of device register to read from * @return the 8bit value read */ uint8_t PCIEDMA_IF::read8(uint32_t offset) { dma_rw_ioctl_t rw; uint8_t val; rw.reg = offset; rw.value = 0; if (ioctl(hDev, IOCTL_PCIEDMA_READ_8BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: read8 failed! \n")); } val = (uint8_t)rw.value; return(val); } /** * Write 8 bits to an LSC hardware register via PCIe bus. * @param offset BAR + address of device register to write to * @param val value to write into the register */ void PCIEDMA_IF::write8(uint32_t offset, uint8_t val) { dma_rw_ioctl_t rw; rw.reg = offset; rw.value = val; if (ioctl(hDev, IOCTL_PCIEDMA_WRITE_8BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: write8 failed! \n")); } } /** * Read 16 bits from an FPGA register via PCIe bus. * @param offset BAR + address of device register to read from * @param val location of storage for data read * @return uint16 value read */ uint16_t PCIEDMA_IF::read16(uint32_t offset) { dma_rw_ioctl_t rw; uint16_t val; rw.reg = offset; rw.value = 0; if (ioctl(hDev, IOCTL_PCIEDMA_READ_16BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: read16 failed! \n")); } val = (uint16_t)rw.value; return(val); } /** * Write 16 bits to an SC hardware register via PCIe bus. * @param offset address of device register to write to * @param val value to write into the register * @return true; error in writing will cause hardware exception */ void PCIEDMA_IF::write16(uint32_t offset, uint16_t val) { dma_rw_ioctl_t rw; rw.reg = offset; rw.value = val; if (ioctl(hDev, IOCTL_PCIEDMA_WRITE_16BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: write16 failed! \n")); } } /** * Read 32 bits from an SC hardware register via PCIe bus. * This is done with 2 16 bit reads because the SC900 only has a 16 bit wide * data bus and will not allow accesses larger than 16 bits. * @param offset address of device register to read from * @param val location of storage for data read * @return true; false if address not multiple of 4 * @note error in reading will cause hardware exception */ uint32_t PCIEDMA_IF::read32(uint32_t offset) { dma_rw_ioctl_t rw; uint32_t val; rw.reg = offset; rw.value = 0; if (ioctl(hDev, IOCTL_PCIEDMA_READ_32BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: read32 failed! \n")); } val = (uint32_t)rw.value; return(val); } /** * Write 32 bits to an SC hardware register via PCIe bus. * @param offset address of device register to write to * @param val value to write into the register * @note error in reading will cause hardware exception */ void PCIEDMA_IF::write32(uint32_t offset, uint32_t val) { dma_rw_ioctl_t rw; rw.reg = offset; rw.value = val; if (ioctl(hDev, IOCTL_PCIEDMA_WRITE_32BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: write32 failed! \n")); } } /** * Read 32 bits from an SC hardware register via PCIe bus. * This is done with 2 16 bit reads because the SC900 only has a 16 bit wide * data bus and will not allow accesses larger than 16 bits. * @param offset address of device register to read from * @param val location of storage for data read * @return true; false if address not multiple of 4 * @note error in reading will cause hardware exception */ uint32_t PCIEDMA_IF::readPCI(uint32_t offset) { dma_rw_ioctl_t rw; uint32_t val; rw.reg = offset; rw.value = 0; if (ioctl(hDev, IOCTL_PCIEDMA_CFG_REG_READ, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: readPCI failed! \n")); } val = (uint32_t)rw.value; return(val); } /** * Write 32 bits to an SC hardware register via PCIe bus. * @param offset address of device register to write to * @param val value to write into the register * @note error in reading will cause hardware exception */ void PCIEDMA_IF::writePCI(uint32_t offset, uint32_t val) { dma_rw_ioctl_t rw; rw.reg = offset; rw.value = val; if (ioctl(hDev, IOCTL_PCIEDMA_CFG_REG_WRITE, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: write32 failed! \n")); } } /** * Read a block of 8 bit registers from FPGA hardware via PCIe bus. * @param offset BAR + address of device register to read from * @param val location of storage for data read * @param len number of bytes to read * @param incAddr true if address increments for each operation, false to * access the same address each time (FIFO access) * @return true; error if driver fails */ bool PCIEDMA_IF::read8(uint32_t offset, uint8_t *val, size_t len, bool incAddr) { dma_rw_ioctl_t rw; uint8_t *buf8; size_t i; buf8 = new uint8_t[MaxBlockSize]; if (buf8 == NULL) return(false); if (incAddr) { // loop to copy len bytes from BAR addr to user's array for (i = 0; i < len; i++) { rw.reg = offset; rw.value = 0; if (ioctl(hDev, IOCTL_PCIEDMA_READ_8BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: block read8 failed! \n")); } *val = (uint8_t)rw.value; //printf("multi read addr:0x%x, value: 0x%x\n", temp_addr,buf8[i]); offset += 1; ++val; } } return(true); } /** * Write a block of 8 bit registers into FPGA hardware via PCIe bus. * @param offset address of device register to write to * @param val location of bytes to write * @param len number of bytes to write * @param incAddr true if address increments for each operation, false to * access the same address each time (FIFO access) * @return true; error in writing will cause hardware exception */ bool PCIEDMA_IF::write8(uint32_t offset, uint8_t *val, size_t len, bool incAddr) { dma_rw_ioctl_t rw; size_t i; if (incAddr) { // loop to copy len bytes from user's array to BAR addr for (i = 0; i < len; i++) { rw.reg = offset; rw.value = *val; if (ioctl(hDev, IOCTL_PCIEDMA_WRITE_8BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: block write8 failed! \n")); } //printf("multi write addr:0x%x, value: 0x%x\n", wr_addr,buf8[i]); offset += 1; ++val; } } return(true); } /** * Read a block of 16 bit registers from SC hardware via PCIe bus. * @param offset address of device registers to read from * @param val location of storage for data read * @param len number of 16 bit words to read (not byte count) * @param incAddr true if address increments for each operation, false to * access the same address each time (FIFO access) * @return true; false if address not multiple of 2 * @note error in reading will cause hardware exception */ bool PCIEDMA_IF::read16(uint32_t offset, uint16_t *val, size_t len, bool incAddr) { dma_rw_ioctl_t rw; size_t i; if (incAddr) { // loop to copy len bytes from BAR addr to user's array for (i = 0; i < len; i++) { rw.reg = offset; rw.value = 0; if (ioctl(hDev, IOCTL_PCIEDMA_READ_16BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: block read16 failed! \n")); } *val = (uint16_t)rw.value; //printf("multi read addr:0x%x, value: 0x%x\n", temp_addr,buf8[i]); offset += 2; ++val; } } return(true); } /** * Write a block of 16 bit registers into FPGA hardware via PCIe bus. * @param offset BAR + address of device registers to write to * @param val location of 16 bit words to write * @param len number of 16 bit words to write * @param incAddr true if address increments for each operation, false to * access the same address each time (FIFO access) * @return true; false if address not multiple of 2 */ bool PCIEDMA_IF::write16(uint32_t offset, uint16_t *val, size_t len, bool incAddr) { dma_rw_ioctl_t rw; size_t i; if (incAddr) { // loop to copy len bytes from user's array to BAR addr for (i = 0; i < len; i++) { rw.reg = offset; rw.value = *val; if (ioctl(hDev, IOCTL_PCIEDMA_WRITE_16BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: block write16 failed! \n")); } offset += 2; ++val; } } return(true); } /** * Read a block of 32 bit registers from FPGA hardware via PCIe bus. * @param offset BAR + address of device registers to read from * @param val location of storage for data read * @param len number of 32 bit words to read (not byte count) * @param incAddr true if address increments for each operation, false to * access the same address each time (FIFO access) * @return true; false if address not multiple of 4 or read failed */ bool PCIEDMA_IF::read32(uint32_t offset, uint32_t *val, size_t len, bool incAddr) { dma_rw_ioctl_t rw; size_t i; if (incAddr) { // loop to copy len bytes from BAR addr to user's array for (i = 0; i < len; i++) { rw.reg = offset; rw.value = 0; if (ioctl(hDev, IOCTL_PCIEDMA_READ_32BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: block read32 failed! \n")); } *val = (uint32_t)rw.value; //printf("multi read addr:0x%x, value: 0x%x\n", temp_addr,buf8[i]); offset += 4; ++val; } } return(true); } /** * Write a block of 32 bit registers into SC hardware via PCIe bus. * @param offset address of device registers to write to * @param val location of 32 bit words to write into SC * @param len number of 32 bit words to write * @param incAddr true if address increments for each operation, false to * access the same address each time (FIFO access) * @return true; false if address not multiple of 4 */ bool PCIEDMA_IF::write32(uint32_t offset, uint32_t *val, size_t len, bool incAddr) { dma_rw_ioctl_t rw; size_t i; if (incAddr) { // loop to copy len bytes from user's array to BAR addr for (i = 0; i < len; i++) { rw.reg = offset; rw.value = *val; if (ioctl(hDev, IOCTL_PCIEDMA_WRITE_32BIT, &rw) != 0) { throw(PCIEDMA_IF_Error("PCIEDMA_IF: block write32 failed! \n")); } offset += 4; ++val; } } return(true); } /** * Open a device driver via a device interface and return the Linux file handle. * The device to open is specified using the /dev filename (pointed to by the GUID) * The instance number of the device can also be specified, such as open the 2nd board of this * type. If the driver supports opening various functions, such as DMA using a * specific channel, then specify this function as a string in the pFunctionName * parameter. * @param hnd reference to caller supplied HANDLE to return open driver * @param pBoardID pointer a string identifying the PCI Vendor_Device pair in board's CFG0 space * @param pDemoID pointer a string identifying the SubSys register values used to identify a Demo * @param instance which instance of that board, if multiple in system * @param pFriendlyName string specifying the Window registery friendly name associated with device (NULL for now) * @param pFunctionName string specifying driver specific function to open (appended to the filename at open time) */ int PCIEDMA_IF::OpenDriver(HANDLE &hnd, const char *pBoardID, const char *pDemoID, uint32_t instance, const char *pFriendlyName, const char *pFunctionName) { int ret = 0; int fd; char drivername[MAX_DEV_FILENAME_SIZE]; if ((pFunctionName != NULL) && (strlen(pFunctionName) >= MAX_FUNCTION_NAME_SIZE)) { ERRORSTR("ERROR: FunctionName string too large\n"); return(PARAM_ERROR); } if ((pBoardID == NULL) || (strlen(pBoardID) > MAX_BOARD_ID_SIZE)) { ERRORSTR("ERROR: invalid BoardID string!\n"); return(PARAM_ERROR); } if ((pDemoID == NULL) || (strlen(pDemoID) > MAX_DEMO_ID_SIZE)) { ERRORSTR("ERROR: invalid DemoID string!\n"); return(PARAM_ERROR); } sprintf(drivername, "/dev/%s_%s_%d", pBoardID, pDemoID, instance); cout << "Opening " << drivername << endl; if (pFunctionName != NULL) { strcat(drivername, pFunctionName); } fd = open(drivername, O_RDWR, 0666); if (fd == -1) { perror("OpenDriver: open"); ERRORSTR("ERROR: Device file not found!\n"); return(OPEN_FILE_FAILED); } hnd = fd; // return the handle to the open device return(ret); }