gfx_test: Draw cursors

Change-Id: I58cd859f3294f30dfd91b87834699d62b1d0b025
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: https://review.coreboot.org/23242
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
Tested-by: Nico Huber <nico.h@gmx.de>
diff --git a/gfxtest/hw-gfx-gma-gfx_test.adb b/gfxtest/hw-gfx-gma-gfx_test.adb
index bf6e917..c8c4f37 100644
--- a/gfxtest/hw-gfx-gma-gfx_test.adb
+++ b/gfxtest/hw-gfx-gma-gfx_test.adb
@@ -83,11 +83,15 @@
       return To_Word (P);
    end Pixel_To_Word;
 
-   Max_W    : constant := 4096;
-   Max_H    : constant := 2160;
-   FB_Align : constant := 16#0004_0000#;
-   subtype Screen_Index is Natural range
-      0 .. 3 * (Max_W * Max_H + FB_Align / 4) - 1;
+   Max_W          : constant := 4096;
+   Max_H          : constant := 2160;
+   FB_Align       : constant := 16#0004_0000#;
+   Cursor_Align   : constant := 16#0001_0000#;
+   Max_Cursor_Wid : constant := 256;
+   subtype Screen_Index is Natural range 0 .. 3 *
+      (Max_W * Max_H + FB_Align / 4 +
+       3 * Max_Cursor_Wid * Max_Cursor_Wid + Cursor_Align / 4)
+      - 1;
    type Screen_Type is array (Screen_Index) of Word32;
 
    function Screen_Offset (FB : Framebuffer_Type) return Natural is
@@ -218,6 +222,45 @@
       end loop;
    end Test_Screen;
 
+   function Donut (X, Y, Max : Cursor_Pos) return Byte
+   is
+      ZZ : constant Int32 := Max * Max * 2;
+      Dist_Center : constant Int32 := ((X * X + Y * Y) * 255) / ZZ;
+      Dist_Circle : constant Int32 := Dist_Center - 20;
+   begin
+      return Byte (255 - Int32'Min (255, 6 * abs Dist_Circle + 64));
+   end Donut;
+
+   procedure Draw_Cursor (Pipe : Pipe_Index; Cursor : Cursor_Type)
+   is
+      use type HW.Byte;
+      Width : constant Width_Type := Cursor_Width (Cursor.Size);
+      Screen_Offset : Natural :=
+         Natural (Shift_Left (Word32 (Cursor.GTT_Offset), 12) / 4);
+   begin
+      if Cursor.Mode /= ARGB_Cursor then
+         return;
+      end if;
+      for Y in Cursor_Pos range -Width / 2 .. Width / 2 - 1 loop
+         for X in Cursor_Pos range -Width / 2 .. Width / 2 - 1 loop
+            declare
+               D : constant Byte := Donut (X, Y, Width / 2);
+            begin
+               -- Hardware seems to expect pre-multiplied alpha (i.e.
+               -- color components already contain the alpha).
+               Screen.Write
+                 (Index => Screen_Offset,
+                  Value => Pixel_To_Word (
+                    (Red   => (if Pipe = Secondary then D / 2 else 0),
+                     Green => (if Pipe = Tertiary  then D / 2 else 0),
+                     Blue  => (if Pipe = Primary   then D / 2 else 0),
+                     Alpha => D)));
+               Screen_Offset := Screen_Offset + 1;
+            end;
+         end loop;
+      end loop;
+   end Draw_Cursor;
+
    procedure Calc_Framebuffer
      (FB       :    out Framebuffer_Type;
       Mode     : in     Mode_Type;
@@ -256,6 +299,50 @@
       Offset := Offset + Word32 (FB_Size (FB));
    end Calc_Framebuffer;
 
+   type Cursor_Array is array (Cursor_Size) of Cursor_Type;
+   Cursors : array (Pipe_Index) of Cursor_Array;
+
+   procedure Prepare_Cursors
+     (Cursors  :    out Cursor_Array;
+      Offset   : in out Word32)
+   is
+      GMA_Phys_Base      : constant PCI.Index := 16#5c#;
+      GMA_Phys_Base_Mask : constant := 16#fff0_0000#;
+
+      Phys_Base : Word32;
+      Success : Boolean;
+   begin
+      Dev.Read32 (Phys_Base, GMA_Phys_Base);
+      Phys_Base := Phys_Base and GMA_Phys_Base_Mask;
+      Success := Phys_Base /= GMA_Phys_Base_Mask and Phys_Base /= 0;
+      if not Success then
+         Debug.Put_Line ("Failed to read stolen memory base.");
+         return;
+      end if;
+
+      for Size in Cursor_Size loop
+         Offset := (Offset + Cursor_Align - 1) and not (Cursor_Align - 1);
+         declare
+            Width : constant Width_Type := Cursor_Width (Size);
+            GTT_End : constant Word32 := Offset + Word32 (Width * Width) * 4;
+         begin
+            Cursors (Size) :=
+              (Mode        => ARGB_Cursor,
+               Size        => Size,
+               Center_X    => Width,
+               Center_Y    => Width,
+               GTT_Offset  => GTT_Range (Shift_Right (Offset, 12)));
+            while Offset < GTT_End loop
+               GMA.Write_GTT
+                 (GTT_Page       => GTT_Range (Offset / GTT_Page_Size),
+                  Device_Address => GTT_Address_Type (Phys_Base + Offset),
+                  Valid          => True);
+               Offset := Offset + GTT_Page_Size;
+            end loop;
+         end;
+      end loop;
+   end Prepare_Cursors;
+
    Pipes : GMA.Pipe_Configs;
 
    procedure Prepare_Configs (Rotation : Rotation_Type)
@@ -282,6 +369,8 @@
                Pipes (Pipe).Port := GMA.Disabled;
             end if;
          end if;
+         Prepare_Cursors (Cursors (Pipe), Offset);
+         Pipes (Pipe).Cursor := Cursors (Pipe) (Cursor_Size'Val (Rand mod 3));
       end loop;
 
       GMA.Dump_Configs (Pipes);
@@ -376,6 +465,9 @@
                  (Framebuffer => Pipes (Pipe).Framebuffer,
                   Pipe        => Pipe);
             end if;
+            for Size in Cursor_Size loop
+               Draw_Cursor (Pipe, Cursors (Pipe) (Size));
+            end loop;
          end loop;
 
          if Delay_MS >= Primary_Delay_MS + Secondary_Delay_MS then