AdaCore / bb-runtimes

Source repository for the GNAT Bare Metal BSPs
Other
65 stars 51 forks source link

Priority ceiling protocol violation #31

Closed BottCode closed 4 years ago

BottCode commented 4 years ago

Hi, I'm getting trouble implementing the budget overrun detection mechanism at pages 26-29 of this document.

I've a task T which calls Boil procedure. This procedure set the handler. The problem arise when a budget overrun is detected: the attempt to invoke Is_Done procedure raises an exception. Debugging the program, I found that the exception is raised due to priority ceiling protocol (PCP) violation. In s-taprob.adb provided by Adacore, the execution falls into row 99 because a PCP is detected. So, why Caller_Priority > Object.Ceiling is True (and hence program error is raised)? Maybe because the budget overrun handler is being executed by the alarm interrupt? In Adacore's ravenscar implementation, interrupts have a strictly higher priority than normal tasks. Therefore, if an interrupt calls a protected procedure of a protected object belonging to a normal task, a PCP violation will always occur. Isn't it?

Let me know if my explanation isn't clear enough. Below you can see a toy application reproducing the issue.

main.adb

    pragma Profile (Ravenscar);

    with Ada.Real_Time;

    with Budget_Overrun_Detection;
    use Budget_Overrun_Detection;

    with System;

    procedure Main is
    use Ada.Real_Time;
    Next_Period : Ada.Real_Time.Time;
    --  Period larger than budget => overrun.
    Period : constant Ada.Real_Time.Time_Span := Ada.Real_Time.Microseconds(4000000);
    LO_Budget : constant Ada.Real_Time.Time_Span := To_Time_Span (3.0);
    pragma Priority (System.Max_Priority);
    begin
      Next_Period := Ada.Real_Time.Clock + Period;

      loop
        Overrun_Inspector.Inform_Inspector (LO_Budget);
        delay until Next_Period;
        Next_Period := Next_Period + Period;
      end loop;
    end Main;

budget_overrun_detection.ads

      with Ada.Real_Time.Timing_Events;
      use Ada.Real_Time.Timing_Events;

      with System.BB.Threads;
      use System.BB.Threads;

      with Ada.Real_Time;

      with System.Multiprocessors;
      use System.Multiprocessors;

      package Budget_Overrun_Detection is

      type Low_Overrun_Event is new Timing_Event with
           record
                Id : Thread_Id;
           end record;

      Low_Overrun_Happened : array (CPU_Range) of Low_Overrun_Event;
      --  A timing event for each CPU.

      protected Overrun_Inspector is
           procedure Inform_Inspector (For_Time : Ada.Real_Time.Time_Span);
           procedure React_To_Overrun (E : in out Timing_Event);
      end Overrun_Inspector;

      end Budget_Overrun_Detection;

budget_overrun_detection.adb

  with System.OS_Interface;
  use System.OS_Interface;

  with Ada.Text_IO;

  package body Budget_Overrun_Detection is

     protected body Overrun_Inspector is

        procedure Inform_Inspector (For_Time : Ada.Real_Time.Time_Span) is
           use Ada.Real_Time;
           --  CPU_Id : constant CPU := Current_CPU;
           Now : constant Ada.Real_Time.Time := Ada.Real_Time.Clock;
        begin
           Set_Handler
                    (Low_Overrun_Happened (Current_CPU),
                    For_Time + Now,
                    React_To_Overrun'Access);
        end Inform_Inspector;

        procedure React_To_Overrun (E : in out Timing_Event) is
           pragma Unreferenced (E);
        begin
           Ada.Text_IO.Put_Line ("Overrun!!");
        end React_To_Overrun;

     end Overrun_Inspector;

  end Budget_Overrun_Detection;

project file demo.gpr

project Demo is

  for Languages use ("ada");
  for Main use ("main.adb");
  for Source_Dirs use ("src");
  for Object_Dir use "obj";
  for Runtime ("ada") use "ravenscar-full-zynq7000";
  for Target use "arm-eabi";

  package Compiler is
     for Switches ("ada") use ("-g", "-gnatwa", "-gnatQ");
  end Compiler;

  package Builder is
     for Switches ("ada") use ("-g", "-O0");
  end Builder;

end Demo;

I'm using GNAT-arm-elf 2018 CE edition.

BottCode commented 4 years ago

I've just solved changing the Overrun_Inspector spec to:

protected Overrun_Inspector is
    pragma Priority (System.Max_Interrupt_Priority);
    procedure Inform_Inspector (For_Time : Ada.Real_Time.Time_Span);
    procedure React_To_Overrun (E : in out Timing_Event);
end Overrun_Inspector;

This solution works, of course, but I'm wondering if it this the "right way". In that document, there's no any priority setting on the protected object. Maybe that example is wrong?

Fabien-Chouteau commented 4 years ago

Hi @BottCode,

This is actually a rule from the Reference manual:

 If the Ceiling_Locking policy (see D.3) is in effect when a procedure Set_Handler is called, a check is made that the ceiling priority of Handler.all is Interrupt_Priority'Last. If the check fails, Program_Error is raised.

https://www.adaic.org/resources/add_content/standards/12rm/html/RM-D-15.html