HiRTOS_e7372ec1/src/porting_layer/cpu_architectures/armv8r_aarch32/hirtos_cpu_arch_interface-interrupt_handling.adb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
--
--  Copyright (c) 2022-2023, German Rivera
--
--
--  SPDX-License-Identifier: Apache-2.0
--

--
--  @summary HiRTOS to target platform interface for ARMv8-R architecture - Interrupt handling
--

with Generic_Execution_Stack;
with HiRTOS.Interrupt_Handling;
with HiRTOS_Cpu_Arch_Interface.Interrupt_Controller;
with HiRTOS_Cpu_Arch_Interface.Memory_Protection;
with HiRTOS_Cpu_Arch_Interface_Private;
with HiRTOS_Low_Level_Debug_Interface;
with System.Machine_Code;
with Interfaces;

package body HiRTOS_Cpu_Arch_Interface.Interrupt_Handling is
   use ASCII;
   use System.Storage_Elements;
   use HiRTOS_Cpu_Arch_Interface_Private;

   --
   --  Entry point of the EL1 undefined instruction exception handler
   --
   procedure EL1_Undefined_Instruction_Exception_Handler
      with Export,
           External_Name => "el1_undefined_instruction_exception_handler";
   pragma Machine_Attribute (EL1_Undefined_Instruction_Exception_Handler, "naked");

   --
   --  Entry point of the EL1 supervisor call exception handler
   --
   procedure EL1_Supervisor_Call_Exception_Handler
      with Export,
           External_Name => "el1_supervisor_call_exception_handler";
   pragma Machine_Attribute (EL1_Supervisor_Call_Exception_Handler, "naked");

   --
   --  Entry point of the EL1 prefetch abort exception handler
   --
   procedure EL1_Prefetch_Abort_Exception_Handler
      with Export,
           External_Name => "el1_prefetch_abort_exception_handler";
   pragma Machine_Attribute (EL1_Prefetch_Abort_Exception_Handler, "naked");

   --
   --  Entry point of the EL1 data abort exception handler
   --
   procedure EL1_Data_Abort_Exception_Handler
      with Export,
           External_Name => "el1_data_abort_exception_handler";
   pragma Machine_Attribute (EL1_Data_Abort_Exception_Handler, "naked");

   --
   --  Entry point of the EL1 IRQ interrupt handler
   --
   procedure EL1_Irq_Interrupt_Handler
      with Export,
           External_Name => "el1_irq_interrupt_handler";
   pragma Machine_Attribute (EL1_Irq_Interrupt_Handler, "naked");

   --
   --  Entry point of the EL1 FIQ interrupt handler
   --
   procedure EL1_Fiq_Interrupt_Handler
      with Export,
           External_Name => "el1_fiq_interrupt_handler";
   pragma Machine_Attribute (EL1_Fiq_Interrupt_Handler, "naked");

   procedure Interrupt_Handler_Prolog
      with Inline_Always;

   procedure Do_Synchronous_Context_Switch
      with Export,
           External_Name => "do_synchronous_context_switch";
   pragma Machine_Attribute (Do_Synchronous_Context_Switch, "naked");

   procedure Stay_In_Cpu_Privileged_Mode
      with Export,
           External_Name => "stay_in_cpu_privileged_mode";
   pragma Machine_Attribute (Stay_In_Cpu_Privileged_Mode, "naked");

   procedure Handle_Invalid_SVC_Exception
      with Export,
           External_Name => "handle_invalid_svc_exception";
   pragma Machine_Attribute (Handle_Invalid_SVC_Exception, "naked");

   ISR_Stack_Size_In_Bytes : constant := 4 * 1024; -- 4KiB

   package ISR_Stacks_Package is new
      Generic_Execution_Stack (Stack_Size_In_Bytes => ISR_Stack_Size_In_Bytes);

   ISR_Stacks :
      array (Cpu_Core_Id_Type) of ISR_Stacks_Package.Execution_Stack_Type
         with Linker_Section => ".isr_stack",
              Convention => C,
              Export,
              External_Name => "isr_stacks";

   ------------------------
   -- Get_ISR_Stack_Info --
   ------------------------

   function Get_ISR_Stack_Info (Cpu_Id : Cpu_Core_Id_Type)
      return ISR_Stack_Info_Type
   is
      use type System.Storage_Elements.Integer_Address;
      ISR_Stack_Info : constant ISR_Stack_Info_Type :=
         (Base_Address => ISR_Stacks (Cpu_Id).Stack_Entries'Address,
          Size_In_Bytes => ISR_Stacks (Cpu_Id).Stack_Entries'Size / System.Storage_Unit);
   begin
      return ISR_Stack_Info;
   end Get_ISR_Stack_Info;

   function Valid_ISR_Stack_Pointer (Cpu_Id : Cpu_Core_Id_Type; Stack_Pointer : System.Address)
      return Boolean is
      Min_Valid_Address : constant Integer_Address :=
         To_Integer (ISR_Stacks (Cpu_Id).Stack_Entries'Address);
      Max_Valid_Address : constant Integer_Address :=
         Min_Valid_Address + (ISR_Stacks (Cpu_Id).Stack_Entries'Size / System.Storage_Unit) - 1;
   begin
      return To_Integer (Stack_Pointer) in Min_Valid_Address .. Max_Valid_Address;
   end Valid_ISR_Stack_Pointer;

   --
   --  Inline subprogram to be invoked at the beginning of top-level EL1 interrupt handlers from
   --  which the RTOS scheduler can be called upon exit.
   --
   --  This subprogram first switches the CPU mode to system mode, so that the SYS/USR
   --  stack pointer is used, instead of corresponding exception CPU mode stack
   --  pointer. Then it saves all general purpose registers on the stack. All registers
   --  need to be saved (both caller-saved and callee-saved) because the task
   --  resumed upon returning from the interrupt may be a different task. However,
   --  we need to save all the registers only if the interrupt nesting level was 0
   --  before this interrupt.
   --
   --  @pre  interrupts are disabled at the CPU
   --  @pre  CPU is in one of the exception modes (SVC, IRQ, FIQ, ABT or UND)
   --  @post CPU is in SYS mode
   --
   --  NOTE: We cannot check preconditions, as that would insert code
   --  at the beginning of this subprogram, which would clobber the CPU registers
   --  before we save them.
   --
   procedure Interrupt_Handler_Prolog is
   begin
      System.Machine_Code.Asm (
         --
         --  Save the IRQ mode LR (or other exception mode LR), and SPSR onto the
         --  interrupted context stack and switch to system mode to save the
         --  remaining registers on the interrupted context stack:
         --
         --  NOTE: The IRQ mode LR holds the exception return address and SPSR
         --  is the interrupted mode CPSR.
         --
         "srsdb sp!, %0" & LF &
         "cps   %0" & LF &

         --
         --  Save general-purpose registers on the stack:
         --
         --  NOTE: register r13 (sp) does not need to (and should not) be saved here,
         --  as it is saved in the interrupted task's TCB.
         --
         "push    {r0-r12, r14}" & LF &

         --
         --  Save floating-point registers on the stack:
         --
         --  NOTE: We need to save the floating point registers even if ISRs do not
         --  explicitly use floating point registers, as the compiler may still
         --  generate code to use floating point registers to temporarily save integer
         --  registers for any function.
         --
         "sub  sp, sp, %1" & LF & --  skip alignment hole
         "vmrs r1, fpscr" & LF &
         "push {r1}" & LF &
         "vpush {d0-d15}" & LF &

         --
         --  Call sp = HiRTOS.Enter_Interrupt_Context (sp)
         --
         "mov r0, sp" & LF &
         "bl hirtos_enter_interrupt_context" & LF &
         "mov sp, r0" & LF &
         --
         --  NOTE: At this point sp always points to somewhere in the ISR stack
         --
         --  Set frame pointer to be the same as stack pointer:
         --  (needed for stack unwinding across interrupted contexts)
         --
         "mov     fp, sp",
         Inputs =>
            [Interfaces.Unsigned_8'Asm_Input ("g", CPSR_System_Mode),  --  %0
             Interfaces.Unsigned_8'Asm_Input ("g",
                                              HiRTOS_Cpu_Arch_Parameters.Integer_Register_Size_In_Bytes)], -- %1
         Volatile => True);
   end Interrupt_Handler_Prolog;

   --
   --  Inline subprogram to be invoked at the end of top-level EL1 interrupt
   --  handlers from which the RTOS scheduler can be called upon exit.
   --
   --  It restores the CPU state that was saved by a previous invocation to
   --  Interrupt_Handler_Prolog, and then executes an 'rfeia' instruction.
   --
   --  @pre  interrupts are disabled at the CPU
   --  @pre  CPU is in SYS mode
   --  @post PC = return address from interrupt (next instruction to execute in
   --        interrupted code)
   --  @post current CPU privilege = privilege level of interrupted code
   --
   procedure Interrupt_Handler_Epilog is
   begin
      System.Machine_Code.Asm (
         --
         --  Call sp = HiRTOS.Interrupt_Handling.Exit_Interrupt_Context (sp)
         --
         "mov r0, sp" & LF &
         "bl hirtos_exit_interrupt_context" & LF &
         "mov sp, r0" & LF &
         --
         --  At this point sp points to a task stack, if interrupt nesting level
         --  dropped to 0. Otherwise, it points to somewhere in the ISR stack.
         --

         --
         --  Restore floating-point registers from the stack:
         --
         "vpop    {d0-d15}" & LF &
         "pop     {r1}" & LF &
         "vmsr    fpscr, r1" & LF &
         "add     sp, sp, %0" & LF & --  skip alignment hole

         --
         --  Restore general-purpose registers saved on the stack:
         --
         --  NOTE: register r13 (sp) does not need to be restored here,
         --  as we already restored it from the thread context
         --
         "pop     {r0-r12, r14}" & LF &

         --
         --  Return from EL1 exception, popping PC and CPSR that were saved on the stack:
         --
         "rfeia   sp!",
         Inputs =>
            (Interfaces.Unsigned_8'Asm_Input ("g",
                                              HiRTOS_Cpu_Arch_Parameters.Integer_Register_Size_In_Bytes)), -- %0
         Volatile => True);

      pragma Assert (False);
      loop
         Wait_For_Interrupt;
      end loop;
   end Interrupt_Handler_Epilog;

   ----------------------------------------------------------------------------
   --  Interrupt and Exception Handlers
   ----------------------------------------------------------------------------

   procedure EL1_Undefined_Instruction_Exception_Handler is
   begin
      Interrupt_Handler_Prolog;
      Handle_Undefined_Instruction_Exception;
      Interrupt_Handler_Epilog;
   end EL1_Undefined_Instruction_Exception_Handler;

   procedure Handle_Undefined_Instruction_Exception is
      use type System.Storage_Elements.Integer_Address;
      Faulting_PC : constant System.Storage_Elements.Integer_Address :=
         System.Storage_Elements.To_Integer (HiRTOS.Interrupt_Handling.Get_Interrupted_PC) - 4;
   begin
      HiRTOS_Low_Level_Debug_Interface.Print_String (
         "*** Undefined instruction exception (faulting PC: ");
      HiRTOS_Low_Level_Debug_Interface.Print_Number_Hexadecimal (Interfaces.Unsigned_32 (Faulting_PC));
      HiRTOS_Low_Level_Debug_Interface.Print_String (")" & ASCII.LF);

      raise Program_Error;
   end Handle_Undefined_Instruction_Exception;

   --
   --  SVC instruction exception handler
   --
   --  Register r0 indicates the action to perform:
   --  - 0 perform RTOS task synchronous context switch
   --  - 1 switch to privileged mode and return to the caller
   --
   --  CAUTION: This subprogram cannot use any stack space as we
   --  do not define a stack for SVC mode.
   --
   procedure EL1_Supervisor_Call_Exception_Handler is
   begin
      System.Machine_Code.Asm (
         --  TODO: Change to get the SVC instruction immediate operand
         --  "ldr r0, [lr, #-4]" & LF &
         --  "ubfx r0, r0, #0, #24" & LF &
         "teq r0, #0" & LF & --  RTOS synchronous task context switch?
         "beq do_synchronous_context_switch" & LF &
         "teq r0, #1" & LF & --  Enter_Cpu_Privileged_Mode call?
         "beq stay_in_cpu_privileged_mode" & LF &
         "b handle_invalid_svc_exception",
         Clobber => "r0",
         Volatile => True);
   end EL1_Supervisor_Call_Exception_Handler;

   --
   --  CAUTION: This subprogram cannot use any stack space, before
   --  it calls Interrupt_Handler_Prolog, as we do not define a stack
   --  for SVC mode.
   --
   procedure Do_Synchronous_Context_Switch is
   begin
      --  Save the current thread's CPU state on its own stack
      Interrupt_Handler_Prolog;

      --  Run the thread scheduler to select next thread to run and
      --  resume execution of the newly selected thread
      Interrupt_Handler_Epilog;
   end Do_Synchronous_Context_Switch;

   procedure Stay_In_Cpu_Privileged_Mode is
   begin
      System.Machine_Code.Asm (
         --
         --  Set CPU mode to system mode in spsr
         --
         "mrs r0, spsr" & LF &
         "orr r0, r0, %0" & LF &
         "msr spsr, r0" & LF &

         --
         --  Return from the exception:
         --  (see Cortex-R5 Technical Reference Manual, section 3.8.1)
         --
         --  NOTE: For the SVC exception, lr points the instruction right
         --  after the svc instruction that brought us here.
         --
         "movs pc, lr",
         Inputs => Interfaces.Unsigned_8'Asm_Input ("g", CPSR_System_Mode), --  %0
         Clobber => "r0",
         Volatile => True);
   end Stay_In_Cpu_Privileged_Mode;

   procedure Handle_Invalid_SVC_Exception is
   begin
      raise Program_Error;
   end Handle_Invalid_SVC_Exception;

   procedure EL1_Prefetch_Abort_Exception_Handler is
   begin
      Interrupt_Handler_Prolog;
      Memory_Protection.Handle_Prefetch_Abort_Exception;
      Interrupt_Handler_Epilog;
   end EL1_Prefetch_Abort_Exception_Handler;

   procedure EL1_Data_Abort_Exception_Handler is
   begin
      Interrupt_Handler_Prolog;
      Memory_Protection.Handle_Data_Abort_Exception;
      Interrupt_Handler_Epilog;
   end EL1_Data_Abort_Exception_Handler;

   procedure EL1_Irq_Interrupt_Handler is
   begin
      --
      --  Adjust lr to point to the instruction we need to return to after
      --  handling the interrupt
      --
      System.Machine_Code.Asm (
         "sub lr, lr, %0",
         Inputs =>
            (Interfaces.Unsigned_8'Asm_Input ("g",
                                              HiRTOS_Cpu_Arch_Parameters.Integer_Register_Size_In_Bytes)), -- %0
         Volatile => True);

      --  Save the current thread's CPU state on its own stack
      Interrupt_Handler_Prolog;

      HiRTOS_Cpu_Arch_Interface.Interrupt_Controller.GIC_Interrupt_Handler (
         HiRTOS_Cpu_Arch_Interface.Interrupt_Controller.Cpu_Interrupt_Irq);

      --  Run the thread scheduler to select next thread to run and
      --  resume execution of the newly selected thread
      Interrupt_Handler_Epilog;
   end EL1_Irq_Interrupt_Handler;

   procedure EL1_Fiq_Interrupt_Handler is
   begin
      --
      --  Adjust lr to point to the instruction we need to return to after
      --  handling the interrupt (see table B1-7, section B1.8.3 in ARMv7-AR ARM)
      --
      System.Machine_Code.Asm (
         "sub lr, lr, %0",
         Inputs =>
            (Interfaces.Unsigned_8'Asm_Input ("g",
                                              HiRTOS_Cpu_Arch_Parameters.Integer_Register_Size_In_Bytes)), -- %0
         Volatile => True);

      --  Save the current thread's CPU state on its own stack
      Interrupt_Handler_Prolog;

      HiRTOS_Cpu_Arch_Interface.Interrupt_Controller.GIC_Interrupt_Handler (
         HiRTOS_Cpu_Arch_Interface.Interrupt_Controller.Cpu_Interrupt_Fiq);

      --  Run the thread scheduler to select next thread to run and
      --  resume execution of the newly selected thread
      Interrupt_Handler_Epilog;
   end EL1_Fiq_Interrupt_Handler;

end HiRTOS_Cpu_Arch_Interface.Interrupt_Handling;