------------------------------------------------------------------------------
--                                                                          --
--                  GNAT RUN-TIME LIBRARY (GNARL) COMPONENTS                --
--                                                                          --
--                         S Y S T E M . B B . T I M E                      --
--                                                                          --
--                                  B o d y                                 --
--                                                                          --
--        Copyright (C) 1999-2002 Universidad Politecnica de Madrid         --
--             Copyright (C) 2003-2005 The European Space Agency            --
--                     Copyright (C) 2003-2010, AdaCore                     --
--                                                                          --
-- GNARL is free software; you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 2,  or (at your option) any later ver- --
-- sion. GNARL is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
-- for  more details.  You should have  received  a copy of the GNU General --
-- Public License  distributed with GNARL; see file COPYING.  If not, write --
-- to  the Free Software Foundation,  59 Temple Place - Suite 330,  Boston, --
-- MA 02111-1307, USA.                                                      --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
-- GNARL was developed by the GNARL team at Florida State University.       --
-- Extensive contributions were provided by Ada Core Technologies, Inc.     --
--                                                                          --
-- The porting of GNARL to bare board  targets was initially  developed  by --
-- the Real-Time Systems Group at the Technical University of Madrid.       --
--                                                                          --
------------------------------------------------------------------------------

pragma Restrictions (No_Elaboration_Code);

with System.BB.Interrupts;
with System.BB.Peripherals;
with System.BB.Protection;
with System.BB.Threads.Queues;

package body System.BB.Time is

   use Peripherals;

   --  We use two timers with the same frequency:
   --     A Periodic Timer for the clock
   --     An Alarm Timer for delays

   -----------------------
   -- Local definitions --
   -----------------------

   type Clock_Periods is mod 2 ** 32;
   for Clock_Periods'Size use 32;
   --  Values of this type represent number of times that the clock finishes
   --  its countdown. This type should allow atomic reads and updates.

   Period_Counter : Clock_Periods := 0;
   pragma Atomic (Period_Counter);
   --  The Period_Counter determines the most significant part of the clock,
   --  which can be retrieved by the MSP_Clock function. The least significant
   --  part of the clock is held in the hardware clock register. The range
   --  of this register is in Parameters.Timer_Interval'Range, but may be
   --  less. The actual time is computed by adding the MSP_Clock and the
   --  hardware clock.

   --  We need to make Period_Counter atomic, because it is updated by the
   --  Clock_Handler. If we would read the Clock_Periods in two parts,
   --  it might be updated between both reads, leading to a result that
   --  may be either too high or too low.

   Pending_Alarm : Boolean := False;
   pragma Atomic (Pending_Alarm);
   --  Alarm Timer is used to set alarms between two periodic interrupts. But
   --  it is possible than due to calculations delay an alarm could expire
   --  after the clock interrupt. If so the Clock Handler does nothing with
   --  alarms, so that this flag shows if an alarm is pending.

   -----------------------
   -- Local subprograms --
   -----------------------

   procedure Alarm_Handler (Interrupt : Interrupts.Interrupt_ID);
   --  Handler for the alarm interrupt

   procedure Clock_Handler (Interrupt : Interrupts.Interrupt_ID);
   --  Handler for the clock interrupt

   function MSP_Clock return Time;
   pragma Inline (MSP_Clock);
   --  Compute the most significant part of Clock based on the
   --  Period_Counter and Max_Timer_Interval;

   -------------------
   -- Alarm_Handler --
   -------------------

   procedure Alarm_Handler (Interrupt : Interrupts.Interrupt_ID) is
      Now             : Time;
      Time_Difference : Time;
      Wakeup_Thread   : Threads.Thread_Id;

      use type Threads.Thread_States;

   begin
      --  Make sure we are handling the right interrupt and there is an alarm
      --  pending.

      pragma Assert
        (Pending_Alarm and then
         Interrupt = Peripherals.Clock_Interrupt_ID);

      Peripherals.Clear_Alarm_Interrupt;

      Now := Clock;

      --  The access to the queues must be protected

      Protection.Enter_Kernel;

      --  Extract all the threads whose delay has expired

      while Threads.Queues.Get_Next_Alarm_Time <= Now loop

         --  Extract the task(s) that was waiting in the alarm queue and insert
         --  it in the ready queue.

         Wakeup_Thread := Threads.Queues.Extract_First_Alarm;

         --  We can only awake tasks that are delay statement

         pragma Assert (Wakeup_Thread.State = Threads.Delayed);

         Wakeup_Thread.State := Threads.Runnable;

         Threads.Queues.Insert (Wakeup_Thread);
      end loop;

      Time_Difference := Threads.Queues.Get_Next_Alarm_Time - Now;

      --  if next alarm time is closer than a clock period then we need to
      --  program the timer. Otherwise we just need to signal that the timer
      --  has not been programed.

      if Time_Difference <= Time (Max_Timer_Interval) then

         --  The timer that we are setting is always in the future because we
         --  have previously checked that the the value of the alarm is
         --  strictly greater than the selected clock value.

         pragma Assert (Time_Difference > 0);

         Peripherals.Set_Alarm (Timer_Interval (Time_Difference));

      else
         Pending_Alarm := False;
      end if;

      --  We have finished the modifications to the queues

      Protection.Leave_Kernel;
   end Alarm_Handler;

   -----------
   -- Clock --
   -----------

   function Clock return Time is
      Before : Time;
      Result : Time;
      After  : Time;

   begin
      --  If MSP_Clock gets updated during the following three statements,
      --  Result may be the sum of the old MSP and wrapped LSP, which would
      --  result in a result that precedes the time of entry of this function.
      --  Before and After will be different. Since the most significant
      --  part of the counter is both read and updated atomically, we know
      --  that After represents the time of the clock interrupt.

      Before := MSP_Clock;
      Result := Before + Time (Read_Clock);
      After  := MSP_Clock;

      return (if After > Result then After else Result);
   end Clock;

   -------------------
   -- Clock_Handler --
   -------------------

   procedure Clock_Handler (Interrupt : Interrupts.Interrupt_ID) is
      Time_Difference : Time;

   begin
      --  Check that we are in the right handler

      pragma Assert (Interrupt = Clock_Interrupt_ID);

      Clear_Clock_Interrupt;

      --  The access to the queues must be protected

      Protection.Enter_Kernel;

      --  The clock timer has finished counting Max_Timer_Interval + 1 hardware
      --  clock ticks, so we increase the Most Significant Part of the Clock
      --  which is kept in memory.

      Period_Counter := Period_Counter + 1;

      if not Pending_Alarm then
         Time_Difference := Threads.Queues.Get_Next_Alarm_Time
                               - (MSP_Clock or Time (Read_Clock));

         --  If next alarm time is closer than a clock period then we need
         --  to program the alarm.

         if Time_Difference <= Time (Max_Timer_Interval) then
            --  Program the alarm at least one tick later

            Set_Alarm (Timer_Interval'Max
                         (Timer_Interval (Time_Difference), 1));

            Pending_Alarm := True;
         end if;
      end if;

      Protection.Leave_Kernel;
   end Clock_Handler;

   -----------------
   -- Delay_Until --
   -----------------

   procedure Delay_Until (T : Time) is
      Now               : Time;
      Time_Difference   : Time;
      Self              : Threads.Thread_Id;
      Inserted_As_First : Boolean;

   begin
      Now := Clock;

      Protection.Enter_Kernel;

      Self := Threads.Thread_Self;

      --  Test if the alarm time is in the future

      if T > Now then

         --  Extract the thread from the ready queue. When a thread wants
         --  to wait for an alarm it becomes blocked.

         Self.State := Threads.Delayed;

         Threads.Queues.Extract (Self);

         --  Insert Thread_Id in the alarm queue (ordered by time) and if it
         --  was inserted at head then check if Alarm Time is closer than the
         --  next clock interrupt.

         Threads.Queues.Insert_Alarm (T, Self, Inserted_As_First);

         if Inserted_As_First then
            Time_Difference := T - Now;

            --  Check whether the alarm time is within a clock period

            if Time_Difference <= Time (Max_Timer_Interval) then

               --  If so, set a new alarm and cancel the previous one if
               --  needed. The timer that we are setting is always in the
               --  future because we have previously checked that the the value
               --  of the alarm is strictly greater than the selected clock
               --  value.

               pragma Assert (Time_Difference > 0);

               if Pending_Alarm then
                  Peripherals.Cancel_And_Set_Alarm
                    (Timer_Interval (Time_Difference));
               else
                  Peripherals.Set_Alarm (Timer_Interval (Time_Difference));
                  Pending_Alarm := True;
               end if;
            end if;
         end if;

      else
         --  If alarm time is not in the future, the thread must yield the CPU

         Threads.Queues.Yield (Self);
      end if;

      Protection.Leave_Kernel;
   end Delay_Until;

   -----------------------
   -- Initialize_Timers --
   -----------------------

   procedure Initialize_Timers is
   begin
      --  Install clock handler

      Interrupts.Attach_Handler (Clock_Handler'Access, Clock_Interrupt_ID);

      --  Install timer handler

      Interrupts.Attach_Handler (Alarm_Handler'Access, Alarm_Interrupt_ID);
   end Initialize_Timers;

   ---------------
   -- MSP_Clock --
   ---------------

   function MSP_Clock return Time is
   begin
      return Time (Period_Counter) * (Time (Max_Timer_Interval) + 1);
      --  Note: this multiplication will typically be a shift
   end MSP_Clock;

end System.BB.Time;
