gfx_test: Refactor animation loop and handle hotplug events

This had a little too many indentation levels already. So move
much of the main loop into its own procedure `Run_The_Show()`.

In all inner loops, we regularly check for hotplug events now
(every 500ms). To avoid tracking the time passed even in cases
when we got interrupted by a hotplug event, we switch to a glo-
bal deadline. This also gives us a more predictable runtime.

Change-Id: Ib3bfc462e5be9a7a08fec5a8ead77c8631674bcf
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/libgfxinit/+/35721
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
diff --git a/gfxtest/hw-gfx-gma-gfx_test.adb b/gfxtest/hw-gfx-gma-gfx_test.adb
index fc1868f..e5bc393 100644
--- a/gfxtest/hw-gfx-gma-gfx_test.adb
+++ b/gfxtest/hw-gfx-gma-gfx_test.adb
@@ -17,6 +17,7 @@
 
    Primary_Delay_MS     : constant := 8_000;
    Secondary_Delay_MS   : constant := 4_000;
+   HP_Delay_MS          : constant :=   500;
    Seed                 : constant := 12345;
 
    package Rand_P is new Ada.Numerics.Discrete_Random (Natural);
@@ -373,8 +374,10 @@
    end Prepare_Configs;
 
    procedure Script_Cursors
-     (Pipes    : in out GMA.Pipe_Configs;
-      Time_MS  : in     Natural)
+     (Pipes          : in out GMA.Pipe_Configs;
+      Hotplug_List   :    out Display_Probing.Port_List;
+      Total_Deadline : in     Time.T;
+      Time_MS        : in     Natural)
    is
       type Corner is (UL, UR, LR, LL);
       type Cursor_Script_Entry is record
@@ -388,9 +391,11 @@
          (LL,  16, -16), (LL,  16, -16), (LL,  16, -16), (LL,   0,  32), (LL,  16, -16));
 
       Deadline : constant Time.T := Time.MS_From_Now (Time_MS);
+      HP_Deadline : Time.T := Time.MS_From_Now (HP_Delay_MS);
       Timed_Out : Boolean := False;
       Cnt : Word32 := 0;
    begin
+      Hotplug_List := (others => Disabled);
       loop
          for Pipe in Pipe_Index loop
             exit when Pipes (Pipe).Port = GMA.Disabled;
@@ -414,6 +419,18 @@
                GMA.Place_Cursor (Pipe, C.Center_X, C.Center_Y);
             end;
          end loop;
+
+         Timed_Out := Time.Timed_Out (HP_Deadline);
+         if Timed_Out then
+            HP_Deadline := Time.MS_From_Now (HP_Delay_MS);
+            GMA.Display_Probing.Hotplug_Events (Hotplug_List);
+            if Hotplug_List (Hotplug_List'First) /= Disabled then
+               return;
+            end if;
+         end if;
+
+         Timed_Out := Time.Timed_Out (Total_Deadline);
+         exit when Timed_Out;
          Timed_Out := Time.Timed_Out (Deadline);
          exit when Timed_Out;
          Time.M_Delay (160);
@@ -432,9 +449,11 @@
    Cursor_Infos : array (Pipe_Index) of Cursor_Info;
 
    procedure Move_Cursors
-     (Pipes    : in out GMA.Pipe_Configs;
-      Time_MS  : in     Natural;
-      Gen      : in     Rand_P.Generator)
+     (Pipes          : in out GMA.Pipe_Configs;
+      Hotplug_List   :    out Display_Probing.Port_List;
+      Total_Deadline : in     Time.T;
+      Time_MS        : in     Natural;
+      Gen            : in     Rand_P.Generator)
    is
       procedure Select_New_Cursor
         (P  : in     Pipe_Index;
@@ -458,9 +477,11 @@
       end Select_New_Cursor;
 
       Deadline : constant Time.T := Time.MS_From_Now (Time_MS);
+      HP_Deadline : Time.T := Time.MS_From_Now (HP_Delay_MS);
       Timed_Out : Boolean := False;
       Cnt : Word32 := 0;
    begin
+      Hotplug_List := (others => Disabled);
       for Pipe in Pipe_Index loop
          exit when Pipes (Pipe).Port = GMA.Disabled;
          Select_New_Cursor (Pipe, Pipes (Pipe).Cursor, Cursor_Infos (Pipe));
@@ -502,6 +523,18 @@
                end if;
             end;
          end loop;
+
+         Timed_Out := Time.Timed_Out (HP_Deadline);
+         if Timed_Out then
+            HP_Deadline := Time.MS_From_Now (HP_Delay_MS);
+            GMA.Display_Probing.Hotplug_Events (Hotplug_List);
+            if Hotplug_List (Hotplug_List'First) /= Disabled then
+               return;
+            end if;
+         end if;
+
+         Timed_Out := Time.Timed_Out (Total_Deadline);
+         exit when Timed_Out;
          Timed_Out := Time.Timed_Out (Deadline);
          exit when Timed_Out;
          Time.M_Delay (16);   -- ~60 fps
@@ -509,6 +542,91 @@
       end loop;
    end Move_Cursors;
 
+   procedure Run_The_Show (Deadline : Time.T; Gen : Rand_P.Generator)
+   is
+      Timed_Out : Boolean;
+      Hotplug_List : GMA.Display_Probing.Port_List;
+
+      New_Pipes : GMA.Pipe_Configs := Pipes;
+
+      function Rand_Div (Num : Position_Type) return Position_Type is
+        (case Rand (Gen) mod 4 is
+            when 3 => Rand (Gen) mod Num / 3,
+            when 2 => Rand (Gen) mod Num / 2,
+            when 1 => Rand (Gen) mod Num,
+            when others => 0);
+   begin
+      for Pipe in GMA.Pipe_Index loop
+         if Pipes (Pipe).Port /= GMA.Disabled then
+            Test_Screen
+              (Framebuffer => Pipes (Pipe).Framebuffer,
+               Pipe        => Pipe);
+         end if;
+         for Size in Cursor_Size loop
+            Draw_Cursor (Pipe, Cursors (Pipe) (Size));
+         end loop;
+      end loop;
+
+      Cursor_Infos :=
+        (others =>
+           (Color    => Pipe_Index'Val (Rand (Gen) mod 3),
+            Size     => Cursor_Size'Val (Rand (Gen) mod 3),
+            X_Velo   => 3 * Cursor_Rand (Gen),
+            Y_Velo   => 3 * Cursor_Rand (Gen),
+            others   => Cursor_Rand (Gen)));
+
+      Script_Cursors (Pipes, Hotplug_List, Deadline, Primary_Delay_MS);
+      if Hotplug_List (Hotplug_List'First) /= Disabled then
+         return;
+      end if;
+      Timed_Out := Time.Timed_Out (Deadline);
+      if Timed_Out then
+         return;
+      end if;
+
+      Rand_P.Reset (Gen, Seed);
+      loop
+         GMA.Display_Probing.Hotplug_Events (Hotplug_List);
+         if Hotplug_List (Hotplug_List'First) /= Disabled then
+            return;
+         end if;
+         New_Pipes := Pipes;
+         for Pipe in GMA.Pipe_Index loop
+            exit when Pipes (Pipe).Port = Disabled;
+            declare
+               New_FB : Framebuffer_Type renames
+                  New_Pipes (Pipe).Framebuffer;
+               Cursor : Cursor_Type renames New_Pipes (Pipe).Cursor;
+               Width : constant Width_Type :=
+                  Pipes (Pipe).Framebuffer.Width;
+               Height : constant Height_Type :=
+                  Pipes (Pipe).Framebuffer.Height;
+            begin
+               New_FB.Start_X := Position_Type'Min
+                 (Width - 320, Rand_Div (Width));
+               New_FB.Start_Y := Position_Type'Min
+                 (Height - 320, Rand_Div (Height));
+               New_FB.Width := Width_Type'Max
+                 (320, Width - New_FB.Start_X - Rand_Div (Width));
+               New_FB.Height := Height_Type'Max
+                 (320, Height - New_FB.Start_Y - Rand_Div (Height));
+
+               Cursor.Center_X := Rotated_Width (New_FB) / 2;
+               Cursor.Center_Y := Rotated_Height (New_FB) / 2;
+               GMA.Update_Cursor (Pipe, Cursor);
+            end;
+         end loop;
+         GMA.Dump_Configs (New_Pipes);
+         GMA.Update_Outputs (New_Pipes);
+         Move_Cursors
+           (New_Pipes, Hotplug_List, Deadline, Secondary_Delay_MS, Gen);
+         exit when Hotplug_List (Hotplug_List'First) /= Disabled;
+
+         Timed_Out := Time.Timed_Out (Deadline);
+         exit when Timed_Out;
+      end loop;
+   end Run_The_Show;
+
    procedure Print_Usage
    is
    begin
@@ -535,6 +653,10 @@
 
       Gen : Rand_P.Generator;
 
+      Deadline : Time.T;
+      Timed_Out : Boolean;
+      Hotplug_List : GMA.Display_Probing.Port_List;
+
       function iopl (level : Interfaces.C.int) return Interfaces.C.int;
       pragma Import (C, iopl, "iopl");
    begin
@@ -582,87 +704,41 @@
       if Initialized then
          Backup_GTT;
 
-         Prepare_Configs (Rotation, Gen);
+         Deadline := Time.MS_From_Now (Delay_MS);
+         loop
+            Prepare_Configs (Rotation, Gen);
 
-         GMA.Update_Outputs (Pipes);
+            GMA.Update_Outputs (Pipes);
 
-         for Pipe in GMA.Pipe_Index loop
-            if Pipes (Pipe).Port /= GMA.Disabled then
-               Backup_Screen (Pipes (Pipe).Framebuffer);
-               Test_Screen
-                 (Framebuffer => Pipes (Pipe).Framebuffer,
-                  Pipe        => Pipe);
-            end if;
-            for Size in Cursor_Size loop
-               Draw_Cursor (Pipe, Cursors (Pipe) (Size));
-            end loop;
-         end loop;
-
-         Cursor_Infos :=
-           (others =>
-              (Color    => Pipe_Index'Val (Rand (Gen) mod 3),
-               Size     => Cursor_Size'Val (Rand (Gen) mod 3),
-               X_Velo   => 3 * Cursor_Rand (Gen),
-               Y_Velo   => 3 * Cursor_Rand (Gen),
-               others   => Cursor_Rand (Gen)));
-
-         if Delay_MS < Primary_Delay_MS + Secondary_Delay_MS then
-            Script_Cursors (Pipes, Delay_MS);
-         else -- getting bored?
-            Script_Cursors (Pipes, Primary_Delay_MS);
-            Delay_MS := Delay_MS - Primary_Delay_MS;
-            declare
-               New_Pipes : GMA.Pipe_Configs := Pipes;
-
-               function Rand_Div (Num : Position_Type) return Position_Type is
-                 (case Rand (Gen) mod 4 is
-                     when 3 => Rand (Gen) mod Num / 3,
-                     when 2 => Rand (Gen) mod Num / 2,
-                     when 1 => Rand (Gen) mod Num,
-                     when others => 0);
-            begin
-               Rand_P.Reset (Gen, Seed);
-               while Delay_MS >= Secondary_Delay_MS loop
-                  New_Pipes := Pipes;
-                  for Pipe in GMA.Pipe_Index loop
-                     exit when Pipes (Pipe).Port = Disabled;
-                     declare
-                        New_FB : Framebuffer_Type renames
-                           New_Pipes (Pipe).Framebuffer;
-                        Cursor : Cursor_Type renames New_Pipes (Pipe).Cursor;
-                        Width : constant Width_Type :=
-                           Pipes (Pipe).Framebuffer.Width;
-                        Height : constant Height_Type :=
-                           Pipes (Pipe).Framebuffer.Height;
-                     begin
-                        New_FB.Start_X := Position_Type'Min
-                          (Width - 320, Rand_Div (Width));
-                        New_FB.Start_Y := Position_Type'Min
-                          (Height - 320, Rand_Div (Height));
-                        New_FB.Width := Width_Type'Max
-                          (320, Width - New_FB.Start_X - Rand_Div (Width));
-                        New_FB.Height := Height_Type'Max
-                          (320, Height - New_FB.Start_Y - Rand_Div (Height));
-
-                        Cursor.Center_X := Rotated_Width (New_FB) / 2;
-                        Cursor.Center_Y := Rotated_Height (New_FB) / 2;
-                        GMA.Update_Cursor (Pipe, Cursor);
-                     end;
-                  end loop;
-                  GMA.Dump_Configs (New_Pipes);
-                  GMA.Update_Outputs (New_Pipes);
-                  Move_Cursors (New_Pipes, Secondary_Delay_MS, Gen);
-                  Delay_MS := Delay_MS - Secondary_Delay_MS;
+            if not (for all P in Pipe_Index => Pipes (P).Port = Disabled) then
+               for Pipe in GMA.Pipe_Index loop
+                  if Pipes (Pipe).Port /= GMA.Disabled then
+                     Backup_Screen (Pipes (Pipe).Framebuffer);
+                  end if;
                end loop;
-               Move_Cursors (New_Pipes, Delay_MS, Gen);
-            end;
-         end if;
 
-         for Pipe in GMA.Pipe_Index loop
-            if Pipes (Pipe).Port /= GMA.Disabled then
-               Restore_Screen (Pipes (Pipe).Framebuffer);
+               Run_The_Show (Deadline, Gen);
+
+               for Pipe in GMA.Pipe_Index loop
+                  if Pipes (Pipe).Port /= GMA.Disabled then
+                     Restore_Screen (Pipes (Pipe).Framebuffer);
+                  end if;
+               end loop;
+            else
+               loop
+                  Time.M_Delay (HP_Delay_MS);
+                  GMA.Display_Probing.Hotplug_Events (Hotplug_List);
+                  exit when Hotplug_List (Hotplug_List'First) /= Disabled;
+
+                  Timed_Out := Time.Timed_Out (Deadline);
+                  exit when Timed_Out;
+               end loop;
             end if;
+
+            Timed_Out := Time.Timed_Out (Deadline);
+            exit when Timed_Out;
          end loop;
+
          Restore_GTT;
       end if;
    end Main;