#include #include #include #include "ap_int.h" #include "ap_utils.h" #include "ap_cint.h" #include "ap_utils.h" #include "interrupt_manager.h" /* * interrupt_manager() * * The Hardware Funtionality of the Interrupt Manager Core. * * The Interrupt Manager Core is Developed to Handle and Forward the Completion Interrupts from the 7 Acceleration Groups. * * The Goal of each Acceleration Group is to Inform the Linux Kernel Driver for the Completion of the Acceleration Procedure. * The Communication of the FPGA with the Host System is Achieved through a PCIe Bus, thus, the Way to Signal the Driver is to Send MSI Interrupts. * * Sending a MSI is a Responsibility of the FPGA's PCIe Bridge. * The PCIe Bridge Carries a 5-Bit Input to Set the Vector Number of the MSI and a 1-Bit Input which is Used to Trigger the MSI According to the Vector Number. * In the Current Block Design the 2 Inputs of the PCIe Bridge are Connected with the Two Channels of a GPIO Peripheral. * This GPIO from now on will be Recognized as GPIO-MSI. * Writing Values in the Data Registers of the 2 Channels of the GPIO-MSI Leads to Triggering a MSI Interrupt. * * In Older Approaches the Acceleration Scheduler of each Acceleration Group would Simply Access the GPIO-MSI to Send MSI Interrupts on Completion of an Image Process. * This Approach was Proved to be Unreliable Since the Concurrent Access to the GPIO-MSI by Multiple Acceleration Groups * Could Lead to Possible Loss of Interrupts that were NEVER Transmitted. * * The new Approach to Ensure Zero Loss of Interrupts was to Develop the Current Interrupt Manager. * The Interrupt Manager Includes an Array of 7 Registers where each Register Refers to each of the 7 Acceleration Groups. * * Register_Array[0] Refers to AGD0 * Register_Array[1] Refers to AGD1 * Register_Array[2] Refers to AGI0 * Register_Array[3] Refers to AGI1 * Register_Array[4] Refers to AGI2 * Register_Array[5] Refers to AGI3 * Register_Array[6] Refers to AGSG * * When an Acceleration Scheduler of any of the Acceleration Groups Requires to Send an MSI Interrupt for the Completion of its Acceleration Procedure * it Simply Writes a Vector Number Value to the Corresponding Field of the Register Array of the Interrupt Manager as a MSI Request. * The Kernel Driver Identifies the Acceleration Group that "Sent" the MSI by the Vector Number. * * Vector Number:0 --> AGD0 * Vector Number:1 --> AGD1 * Vector Number:2 --> AGI0 * Vector Number:3 --> AGI1 * Vector Number:4 --> AGI2 * Vector Number:5 --> AGI3 * Vector Number:6 --> AGSG * * The Interrupt Manager Checks in a Round Robin Manner the Fields of the Register Array for a Non-Zero Value which Indicates a new MSI Request. * This Makes it Obvious that the Acceleration Schedulers Write to the Register Array of the Interrupt Manager the Vector Number Incremented by 1. * This is Done to Avoid Zero Values that are not Identified by the Interrupt Manager as MSI Requests. * * If the Interrupt Manager Finds a Field of the Register Array with Non-Zero Value then it Decreases this Value by 1 in order to Produce * the Correct Vector Number and Writes this Value to the GPIO-MSI Peripheral to Trigger the MSI Interrupt. * * The Interrupt Manager, then, Waits until it Receives an Acknowledgment Signal from the Kernel Driver before Checking for another MSI Request. * The Kernel Driver, actually, Writes a Logic 1 Value to another GPIO Peripheral whose 1-Bit Output Signals the Interrupt Manager. * This GPIO Peripheral from now on will be Recognized as GPIO-ACK. * * * The Sequential Steps of the Interrupt Management are as Follows: * * Start a for Loop with 7 Iterations where each Iteration is to Check for a MSI Request by the Corresponding Acceleration Group. * NOTE Enabling the Auto Restart Mode of the Current Core will Lead to Starting Over the for Loop. * * a --> Check if the Current Field of the Register Array Has a Non-Zero Value. * If this is the Case Proceed to Send a MSI Interrupt. * b --> Decrease the Value of the Current Field of the Register Array to Get the Correct Vector Number. * c --> Write the Vector Number to the GPIO-MSI Peripheral that is Connected with the PCIe Bridge to Trigger an MSI Interrupt. * d --> Wait for an Acknowledgment Signal from the Driver through the GPIO-ACK Peripheral. * e --> Self-Clear to Zero the Current Field of the Register Array of the Interrupt Manager. * The Next Time we Find a Non-Zero Value in this Field we Know that an Acceleration Group Has Made a Valid MSI Request. * f --> Clear the Data Register of the GPIO-ACk Peripheral. * * The Function Parameters are the Input/Output Ports/Interfaces of the Core: * * 01 --------> The AXI Master Interface of the Core Used to Access External Devices and Memories. * 02 --------> Single Bit Input Used to Receive External Acknowledgements from the Linux Kernel Driver. * 03 to 06 --> Registers of the Core that are Accessed through the AXI Slave Lite Interface of the Core. */ int interrupt_manager(/*01*/volatile ap_uint<32> *ext_cfg, /*02*/volatile ap_uint<1> *intr_ack, /*03*/unsigned int gpio_msi_device_address, /*04*/unsigned int gpio_ack_device_address, /*05*/unsigned int self_msi_request_offset, /*06*/unsigned int msi_request[7] ) { /* * The ext_cfg is the AXI Master Interface of the Core. */ #pragma HLS INTERFACE m_axi port=ext_cfg /* * The gpio_msi_device_address is a Register to Store the Base Address of the GPIO-MSI that this Core * will Need to Access through the ext_cfg AXI Master Interface. * This Register is Accessed through the AXI Slave Lite Interface (cfg) of the Core. */ #pragma HLS INTERFACE s_axilite port=gpio_msi_device_address bundle=cfg /* * The gpio_ack_device_address is a Register to Store the Base Address of the GPIO-ACK that this Core * will Need to Access through the ext_cfg AXI Master Interface. * This Register is Accessed through the AXI Slave Lite Interface (cfg) of the Core. */ #pragma HLS INTERFACE s_axilite port=gpio_ack_device_address bundle=cfg /* * The self_msi_request_offset is a Register to Store the Address Offset where the Register Array (msi_request) is Located. * This Address Offset Actually Leads the Interrupt Manager to Access its Own Configuration Registers through its AXI Slave Lite (cfg) Interface. */ #pragma HLS INTERFACE s_axilite port=self_msi_request_offset bundle=cfg #pragma HLS INTERFACE s_axilite port=msi_request bundle=cfg /* * The intr_ack is a Single Bit Input which is Used to Receive External Acknowledgements from the Linux Kernel Driver. */ #pragma HLS INTERFACE ap_none port=intr_ack #pragma HLS INTERFACE s_axilite port=return bundle=cfg ap_uint<32> data_register; // Used to Temporalily Store Values when Reading or Writing from/to Registers of External Devices. ap_uint<1> intr_ack_value; // Used to Read the Last Value of the intr_ack Input Port. for(int repeat = 0; repeat < 7; repeat++) { //If the Current Field of the Register Array (msi_request) Has a Non-Zero Value then we Have a Valid MSI Request by the Corresponding Acceleration Group. if(msi_request[repeat] != 0) { /* * --------------------------------------------------------- * Send a MSI Interrupt by Writing to the GPIO-MSI Registers * --------------------------------------------------------- */ //Decrease the Value of the Current Field of the Register Array to Get the Correct Vector Number. data_register = msi_request[repeat] - 1; //Write the Vector Number to the Data Register of the Second Channel of the GPIO-MSI. memcpy((ap_uint<32> *)(ext_cfg + (gpio_msi_device_address + XGPIO_DATA_OFFSET + XGPIO_CHANNEL_2_OFFSET) / 4), &data_register, sizeof(ap_uint<32>)); //Write a Logic 1 Value to the Data Register of the First Channel of the GPIO-MSI to Trigger the MSI Interrupt. data_register = 0x1; memcpy((ap_uint<32> *)(ext_cfg + (gpio_msi_device_address + XGPIO_DATA_OFFSET + XGPIO_CHANNEL_1_OFFSET) / 4), &data_register, sizeof(ap_uint<32>)); //Set the Data Register of the First Channel of the GPIO-MSI back to Zero. data_register = 0x0; memcpy((ap_uint<32> *)(ext_cfg + (gpio_msi_device_address + XGPIO_DATA_OFFSET + XGPIO_CHANNEL_1_OFFSET) / 4), &data_register, sizeof(ap_uint<32>)); /* * ------------------------------------ * Wait for a Interrupt Acknowledgement * ------------------------------------ */ //Make an Initial Read of the Current State of the intr_ack Input. intr_ack_value = *intr_ack; //Keep Looping for as long as the intr_ack Input Does not Reach a Logic 1 Value. while(intr_ack_value != 1) { //Keep Reading the Last Value of the intr_ack Input. intr_ack_value = *intr_ack; } //Reset the Reader Variable. intr_ack_value = 0; data_register = 0x0; /* --------------------------------------------------------------------------- * Self-Clear the Current Field of the Register Array of the Interrupt Manager * --------------------------------------------------------------------------- */ //Write a Zero Value to the Current Field of the Register Array of the Interrupt Manager to Clear the Field. //NOTE the Interrupt Manager Herein Uses its AXI Master Interface to Write to its own AXI Slave Lite Interface. memcpy((ap_uint<32> *)(ext_cfg + (self_msi_request_offset + (repeat * 4)) / 4), &data_register, sizeof(ap_uint<32>)); /* * ----------------------------- * Clear the GPIO-ACK Peripheral * ----------------------------- */ //Clear the GPIO-ACK by Writing a Zero Value to its Data Register. memcpy((ap_uint<32> *)(ext_cfg + (gpio_ack_device_address + XGPIO_DATA_OFFSET + XGPIO_CHANNEL_1_OFFSET) / 4), &data_register, sizeof(ap_uint<32>)); } } return 1; }