207 lines
9.7 KiB
C++
207 lines
9.7 KiB
C++
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#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;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|