UPGRADE YOUR BROWSER

We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for 
Search instead for 
Did you mean: 
Observer pstootman
Observer
8,816 Views
Registered: ‎03-29-2015

Routing all interrupts from PL to PS1 (linux on PS0)

Jump to solution

Can anyone share an example device tree source for configuring GIC so that the 16 interrupts from the PL fabric are all routed to (handled by) only PS1 (baremetal) and completely ignored by PS0 (linux) ?

(Or an example of how to route just one interrupt to PS1 will also be great).

 

Is device tree entries the best way to do this GIC configuration ? Or instead should we be writing some C code to run on PS0 to setup the GIC distribution registers (can it be done from userspace application?)

 

My understanding is that these particular GIC registers should not be modified by any code running on PS1 ? (it appears when USE_AMP=1 defined, the BSP for PS1 does not touch the distribution registers when it initialises GIC framework).

 

Thanks.

 

0 Kudos
1 Solution

Accepted Solutions
Observer pstootman
Observer
16,383 Views
Registered: ‎03-29-2015

Re: Routing all interrupts from PL to PS1 (linux on PS0)

Jump to solution

This task was best done in the baremetal PS1 source code, in my case.

 

Xilinx, I think you have a bug in 2015.4 BSP source, in function XScuGic_InterruptMaptoCpu().

 

I made this modified version to use instead;

 

/* Map interrupt to this CPU (which might be PS1, if doing AMP) */
/* Note Xilinx provides this function, but (in 2015.4 SDK) it has a bug in it,
   so we don't use it */
/* void XScuGic_InterruptMaptoCpu(XScuGic *InstancePtr, u8 Cpu_Id, u32 Int_Id) */
void
Set_Interrupt_Target_To_This_CPU( XScuGic *InstancePtr, u32 Int_Id )
{
  u32 RegValue, Offset;

  /* XPAR_CPU_ID (defined in BSP header file xparameters.h) is defined as 0 for PS0, and 1 for PS1 */
  /* Field in ICDIPTR register needs to be set to "01" for PS0, and "10" for PS1 */
  /* So for AMP build for PS1, Cpu_ID shall be "10", and for non-AMP build for PS0, Cpu_ID shall be "01" */
  u32 Cpu_Id = (u32)XPAR_CPU_ID + (u32)1;

  /* Four interrupt ID's are configured per 32-bit register */
  /* ICDIPTR8:  35 downto 32 */
  /* ICDIPTR9:  39 downto 36 */
  /* ... */
  /* ICDIPTR23: 95 downto 92 */

  /* Get existing value of the relevant ICDIPTRx register for this interrupt ID */
  RegValue = XScuGic_DistReadReg(InstancePtr, XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id));

  /* Interrupt ID mod 4 */
  Offset =  (Int_Id & 0x3);

  /* Set byte 3/2/1/0 of the register value to all 0x00 */
  /* This is the line which is wrong in Xilinx BSP XScuGic_InterruptMaptoCpu(), which uses | instead of &,
     and also doesn't ensure 0xFF is 32-bit before shifting */
  RegValue = (RegValue & (~((u32)0xFF << (Offset*8))) );

  /* Set bits 1:0 of the relevant byte (3/2/1/0) to "01" (for PS0) or "10" (for PS1) */
  RegValue |= ((Cpu_Id) << (Offset*8));

  /* Write new register value back to ICDIPTRx */
  XScuGic_DistWriteReg(InstancePtr, XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id), RegValue);
}

 

Below is example of how to configure/route/enable interrupt in baremetal code (for either PS0 or PS1).

 

 

  /* Configure interrupt as low priority, and rising edge triggered */
  XScuGic_SetPriorityTriggerType( &xInterruptController,                                        /* XScuGic *InstancePtr */            /* GIC driver instance structure */
                                  PLPS_INTERRUPT_ID_DFT_COMPLETE,                                /* u32 Int_Id */                      /* Interrupt ID */
                                  portLOWEST_USABLE_INTERRUPT_PRIORITY << portPRIORITY_SHIFT,   /* u8 Priority */                     /* Interrupt priority. Todo review, this is same priority as the FreeRTOS task tick */
                                  XSCUGIC_RISING_EDGE );                                        /* u8 Trigger */                      /* Trigger. Believe GIC driver only supports rising edge or high level sensitive */

  /* Install the handler ISR function */
  Status = XScuGic_Connect( &xInterruptController,                        /* XScuGic *InstancePtr */            /* GIC driver instance structure */
                            PLPS_INTERRUPT_ID_DFT_COMPLETE,               /* u32 Int_Id */                      /* Interrupt ID */
                            (Xil_ExceptionHandler) HWI_DFT_Complete,      /* Xil_InterruptHandler Handler */    /* Interrupt routine function */
                            NULL );                                       /* void *CallBackRef */               /* ISR parameter */
  if ( Status != XST_SUCCESS )
  {
    FATAL_ERROR;
  }

  /* Target the interrupt to this CPU (so that this code can run on PS0 in non-AMP mode, or run on PS1 in AMP mode) */
  Set_Interrupt_Target_To_This_CPU( &xInterruptController,                /*XScuGic *InstancePtr,*/
                                    PLPS_INTERRUPT_ID_DFT_COMPLETE );     /*u32 Interrupt_ID )*/

  /* Enable the interrupt in the interrupt controller */
  XScuGic_Enable( &xInterruptController, PLPS_INTERRUPT_ID_DFT_COMPLETE );

 

I think the reason DistributorInit() doesn't do anything in the BSP GIC source code, when USE_AMP=1, is so that ALL distributor registers are not initialised (which would interfere with linux/PS0). However I think you can still modify the configuration for individual interrupt IDs (linux/PS0 has not configured anything which stops PS1 from directly accessing the physical addresses for the API/mpcore register block).

 

I guess there is small chance of some race condition if both PS0 and PS1 are doing read/modify/write of the ICDIPTR registers at the same time, I can live with this for now, since in my case the chance of something going wrong seems almost negligible, since it is only done once during startup on PS1 (after linux/PS0 already finished booting). If concerned about this I suppose it might help to move above concepts to a userspace application in linux, but then you would need to use /dev/mem and mmap() to access the physical addresses of the distributor registers (and have PS0 configure them on behalf of PS1). Even then, perhaps there is some race between kernel and userspace app, not sure.

 

I am still curious on how to do this task using linux devicetree approach, if anyone knows.

 

 

 

0 Kudos
4 Replies
Observer pstootman
Observer
16,384 Views
Registered: ‎03-29-2015

Re: Routing all interrupts from PL to PS1 (linux on PS0)

Jump to solution

This task was best done in the baremetal PS1 source code, in my case.

 

Xilinx, I think you have a bug in 2015.4 BSP source, in function XScuGic_InterruptMaptoCpu().

 

I made this modified version to use instead;

 

/* Map interrupt to this CPU (which might be PS1, if doing AMP) */
/* Note Xilinx provides this function, but (in 2015.4 SDK) it has a bug in it,
   so we don't use it */
/* void XScuGic_InterruptMaptoCpu(XScuGic *InstancePtr, u8 Cpu_Id, u32 Int_Id) */
void
Set_Interrupt_Target_To_This_CPU( XScuGic *InstancePtr, u32 Int_Id )
{
  u32 RegValue, Offset;

  /* XPAR_CPU_ID (defined in BSP header file xparameters.h) is defined as 0 for PS0, and 1 for PS1 */
  /* Field in ICDIPTR register needs to be set to "01" for PS0, and "10" for PS1 */
  /* So for AMP build for PS1, Cpu_ID shall be "10", and for non-AMP build for PS0, Cpu_ID shall be "01" */
  u32 Cpu_Id = (u32)XPAR_CPU_ID + (u32)1;

  /* Four interrupt ID's are configured per 32-bit register */
  /* ICDIPTR8:  35 downto 32 */
  /* ICDIPTR9:  39 downto 36 */
  /* ... */
  /* ICDIPTR23: 95 downto 92 */

  /* Get existing value of the relevant ICDIPTRx register for this interrupt ID */
  RegValue = XScuGic_DistReadReg(InstancePtr, XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id));

  /* Interrupt ID mod 4 */
  Offset =  (Int_Id & 0x3);

  /* Set byte 3/2/1/0 of the register value to all 0x00 */
  /* This is the line which is wrong in Xilinx BSP XScuGic_InterruptMaptoCpu(), which uses | instead of &,
     and also doesn't ensure 0xFF is 32-bit before shifting */
  RegValue = (RegValue & (~((u32)0xFF << (Offset*8))) );

  /* Set bits 1:0 of the relevant byte (3/2/1/0) to "01" (for PS0) or "10" (for PS1) */
  RegValue |= ((Cpu_Id) << (Offset*8));

  /* Write new register value back to ICDIPTRx */
  XScuGic_DistWriteReg(InstancePtr, XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id), RegValue);
}

 

Below is example of how to configure/route/enable interrupt in baremetal code (for either PS0 or PS1).

 

 

  /* Configure interrupt as low priority, and rising edge triggered */
  XScuGic_SetPriorityTriggerType( &xInterruptController,                                        /* XScuGic *InstancePtr */            /* GIC driver instance structure */
                                  PLPS_INTERRUPT_ID_DFT_COMPLETE,                                /* u32 Int_Id */                      /* Interrupt ID */
                                  portLOWEST_USABLE_INTERRUPT_PRIORITY << portPRIORITY_SHIFT,   /* u8 Priority */                     /* Interrupt priority. Todo review, this is same priority as the FreeRTOS task tick */
                                  XSCUGIC_RISING_EDGE );                                        /* u8 Trigger */                      /* Trigger. Believe GIC driver only supports rising edge or high level sensitive */

  /* Install the handler ISR function */
  Status = XScuGic_Connect( &xInterruptController,                        /* XScuGic *InstancePtr */            /* GIC driver instance structure */
                            PLPS_INTERRUPT_ID_DFT_COMPLETE,               /* u32 Int_Id */                      /* Interrupt ID */
                            (Xil_ExceptionHandler) HWI_DFT_Complete,      /* Xil_InterruptHandler Handler */    /* Interrupt routine function */
                            NULL );                                       /* void *CallBackRef */               /* ISR parameter */
  if ( Status != XST_SUCCESS )
  {
    FATAL_ERROR;
  }

  /* Target the interrupt to this CPU (so that this code can run on PS0 in non-AMP mode, or run on PS1 in AMP mode) */
  Set_Interrupt_Target_To_This_CPU( &xInterruptController,                /*XScuGic *InstancePtr,*/
                                    PLPS_INTERRUPT_ID_DFT_COMPLETE );     /*u32 Interrupt_ID )*/

  /* Enable the interrupt in the interrupt controller */
  XScuGic_Enable( &xInterruptController, PLPS_INTERRUPT_ID_DFT_COMPLETE );

 

I think the reason DistributorInit() doesn't do anything in the BSP GIC source code, when USE_AMP=1, is so that ALL distributor registers are not initialised (which would interfere with linux/PS0). However I think you can still modify the configuration for individual interrupt IDs (linux/PS0 has not configured anything which stops PS1 from directly accessing the physical addresses for the API/mpcore register block).

 

I guess there is small chance of some race condition if both PS0 and PS1 are doing read/modify/write of the ICDIPTR registers at the same time, I can live with this for now, since in my case the chance of something going wrong seems almost negligible, since it is only done once during startup on PS1 (after linux/PS0 already finished booting). If concerned about this I suppose it might help to move above concepts to a userspace application in linux, but then you would need to use /dev/mem and mmap() to access the physical addresses of the distributor registers (and have PS0 configure them on behalf of PS1). Even then, perhaps there is some race between kernel and userspace app, not sure.

 

I am still curious on how to do this task using linux devicetree approach, if anyone knows.

 

 

 

0 Kudos
Observer ajcurtis84
Observer
7,983 Views
Registered: ‎04-04-2016

Re: Routing all interrupts from PL to PS1 (linux on PS0)

Jump to solution

Hello,

 

Was this ever verified as a bug in 2015.4?

 

Thanks

0 Kudos
Observer pstootman
Observer
7,934 Views
Registered: ‎03-29-2015

Re: Routing all interrupts from PL to PS1 (linux on PS0)

Jump to solution

I have received no communications from Xilinx about it.

I thought it was a bug by just reading the code; I never stepped through their code or experienced an execution issue; I just changed the code to my own version as above before even trying to run with original BSP function.

I think 2016.1 code is same as 2015.4 ; so maybe Xilinx doesn't agree it is a bug.

 

===================

 

I realise some things in my post above may be a bit cryptic without this extra info; this may complete the picture if useful;

I believe active high level and rising edge are the only 2 types of interrupts supported from PL to PS;

 

#define PLPS_INTERRUPT_ID_DFT_COMPLETE            XPS_FPGA6_INT_ID

/* For reference info (from xparameters_ps.h):
#define XPS_FPGA0_INT_ID    61U
#define XPS_FPGA1_INT_ID    62U
#define XPS_FPGA2_INT_ID    63U
#define XPS_FPGA3_INT_ID    64U
#define XPS_FPGA4_INT_ID    65U
#define XPS_FPGA5_INT_ID    66U
#define XPS_FPGA6_INT_ID    67U
#define XPS_FPGA7_INT_ID    68U
#define XPS_FPGA8_INT_ID    84U
#define XPS_FPGA9_INT_ID    85U
#define XPS_FPGA10_INT_ID   86U
#define XPS_FPGA11_INT_ID   87U
#define XPS_FPGA12_INT_ID   88U
#define XPS_FPGA13_INT_ID   89U
#define XPS_FPGA14_INT_ID   90U
#define XPS_FPGA15_INT_ID   91U */

#define   XSCUGIC_ACTIVE_HIGH_LEVEL_SENSITIVE   1   /* Active high level sensitive */
#define   XSCUGIC_RISING_EDGE                   3   /* Rising edge triggered */

 

0 Kudos
Observer ajcurtis84
Observer
7,475 Views
Registered: ‎04-04-2016

Re: Routing all interrupts from PL to PS1 (linux on PS0)

Jump to solution
I confirmed that XScuGic_InterruptMaptoCpu() is incorrect.
1. The mask is getting Or'ed when it should be an AND
2. Assuming you are using the xparameter CPU_ID define, you need to add 1 to the CPU id passed in.
0 Kudos