From 1ebd229556ba767f76c829b6d00d8a7b5bf28e65 Mon Sep 17 00:00:00 2001
From: mengyingkun <mengyingkun@loongson.cn>
Date: Wed, 8 Feb 2023 09:24:05 +0800
Subject: [PATCH] loongarch: Add EFI frame buffer support

Signed-off-by: yangqiming <yangqiming@loongson.cn>
Signed-off-by: mengyingkun <mengyingkun@loongson.cn>
---
 grub-core/loader/loongarch64/linux-elf.c | 232 +++++++++++++++++++++++
 include/grub/loongarch64/linux.h         |  47 +++++
 2 files changed, 279 insertions(+)

diff --git a/grub-core/loader/loongarch64/linux-elf.c b/grub-core/loader/loongarch64/linux-elf.c
index 8260e4c..852e8f4 100644
--- a/grub-core/loader/loongarch64/linux-elf.c
+++ b/grub-core/loader/loongarch64/linux-elf.c
@@ -23,6 +23,7 @@
 #include <grub/elfload.h>
 #include <grub/cpu/relocator.h>
 #include <grub/efi/memory.h>
+#include <grub/efi/graphics_output.h>
 
 #define GRUB_ADDRESS_TYPE_SYSRAM	1
 #define GRUB_ADDRESS_TYPE_RESERVED	2
@@ -34,13 +35,242 @@
 	{ 0x89, 0x9a, 0x43, 0x18, 0x02, 0x50, 0xa0, 0xc9 } \
     }
 
+#define GRUB_EFI_LARCH_SCREEN_INFO_GUID \
+  { 0x07fd51a6, 0x9532, 0x926f, \
+    { 0x51, 0xdc, 0x6a, 0x63, 0x60, 0x2f, 0x84, 0xb4 } \
+  }
+
+#define GRUB_EFI_LARCH_CONSOLE_OUT_DEVICE_GUID \
+  { 0xd3b36f2c, 0xd551, 0x11d4,  \
+    { 0x9a, 0x46, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
+  }
+
 static struct grub_relocator *relocator;
+static grub_efi_guid_t screen_info_guid = GRUB_EFI_LARCH_SCREEN_INFO_GUID;
 
 void grub_linux_loongarch_elf_relocator_unload (void)
 {
   grub_relocator_unload (relocator);
 }
 
+static void
+find_bits (unsigned long mask, grub_efi_uint8_t *pos, grub_efi_uint8_t *size)
+{
+  grub_efi_uint8_t first, len;
+
+  first = 0;
+  len = 0;
+
+  if (mask)
+    {
+      while (!(mask & 0x1))
+        {
+    mask = mask >> 1;
+    first++;
+        }
+
+      while (mask & 0x1)
+        {
+    mask = mask >> 1;
+    len++;
+        }
+    }
+
+  *pos = first;
+  *size = len;
+}
+
+static void
+setup_pixel_info (struct screen_info *si, grub_efi_uint32_t pixels_per_scan_line,
+                  struct grub_efi_gop_pixel_bitmask pixel_info, int pixel_format)
+{
+  if (pixel_format == GRUB_EFI_GOT_RGBA8)
+    {
+      si->lfb_depth = 32;
+      si->lfb_linelength = pixels_per_scan_line * 4;
+      si->red_size = 8;
+      si->red_pos = 0;
+      si->green_size = 8;
+      si->green_pos = 8;
+      si->blue_size = 8;
+      si->blue_pos = 16;
+      si->rsvd_size = 8;
+      si->rsvd_pos = 24;
+    }
+  else if (pixel_format == GRUB_EFI_GOT_BGRA8)
+    {
+      si->lfb_depth = 32;
+      si->lfb_linelength = pixels_per_scan_line * 4;
+      si->red_size = 8;
+      si->red_pos = 16;
+      si->green_size = 8;
+      si->green_pos = 8;
+      si->blue_size = 8;
+      si->blue_pos = 0;
+      si->rsvd_size = 8;
+      si->rsvd_pos = 24;
+    }
+  else if (pixel_format == GRUB_EFI_GOT_BITMASK)
+    {
+      find_bits(pixel_info.r, &si->red_pos, &si->red_size);
+      find_bits(pixel_info.g, &si->green_pos, &si->green_size);
+      find_bits(pixel_info.b, &si->blue_pos, &si->blue_size);
+      find_bits(pixel_info.a, &si->rsvd_pos, &si->rsvd_size);
+      si->lfb_depth = si->red_size + si->green_size +
+                      si->blue_size + si->rsvd_size;
+      si->lfb_linelength = (pixels_per_scan_line * si->lfb_depth) / 8;
+    }
+  else
+    {
+      si->lfb_depth = 4;
+      si->lfb_linelength = si->lfb_width / 2;
+      si->red_size = 0;
+      si->red_pos = 0;
+      si->green_size = 0;
+      si->green_pos = 0;
+      si->blue_size = 0;
+      si->blue_pos = 0;
+      si->rsvd_size = 0;
+      si->rsvd_pos = 0;
+    }
+}
+
+static struct screen_info *
+alloc_screen_info (void)
+{
+  grub_efi_status_t status;
+  grub_efi_boot_services_t *b;
+  struct screen_info *si;
+
+  b = grub_efi_system_table->boot_services;
+  status = efi_call_3 (b->allocate_pool, GRUB_EFI_RUNTIME_SERVICES_DATA,
+                           sizeof(*si), (void**)&si);
+  if (status != GRUB_EFI_SUCCESS)
+    return NULL;
+
+  status = b->install_configuration_table (&screen_info_guid, si);
+  if (status == GRUB_EFI_SUCCESS)
+    return si;
+
+  efi_call_1 (b->free_pool, si);
+
+  return NULL;
+}
+
+static struct screen_info *
+setup_screen_info (void)
+{
+  grub_efi_boot_services_t *b;
+  grub_efi_handle_t gop_handle;
+  struct screen_info *si = NULL;
+  struct grub_efi_gop *gop, *first_gop;
+  grub_efi_handle_t *handles;
+  grub_efi_uintn_t num_handles, i;
+  grub_efi_guid_t graphics_output_guid = GRUB_EFI_GOP_GUID;
+  grub_efi_uint16_t width, height;
+  grub_efi_uint32_t ext_lfb_base, pixels_per_scan_line;
+  grub_efi_uint64_t fb_base;
+  struct grub_efi_gop_pixel_bitmask pixel_info;
+  grub_efi_gop_pixel_format_t pixel_format;
+
+  si = alloc_screen_info();
+  if (!si)
+    return NULL;
+
+  handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL,
+                    &graphics_output_guid, NULL, &num_handles);
+  if (!handles || num_handles == 0)
+    goto free_screen_info;
+
+  gop = NULL;
+  first_gop = NULL;
+
+  for (i = 0; i < num_handles; i++)
+    {
+      struct grub_efi_gop_mode *mode;
+      struct grub_efi_gop_mode_info *info = NULL;
+      grub_efi_guid_t conout_proto = GRUB_EFI_LARCH_CONSOLE_OUT_DEVICE_GUID;
+      void *dummy = NULL;
+      grub_efi_uint8_t conout_found = 0;
+      grub_efi_uint64_t current_fb_base;
+
+      gop_handle = handles[i];
+      gop = grub_efi_open_protocol (gop_handle, &graphics_output_guid,
+            GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+
+      dummy = grub_efi_open_protocol (gop_handle, &conout_proto,
+                GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+      if (dummy != NULL)
+        conout_found = 1;
+
+      mode = gop->mode;
+      info = mode->info;
+      current_fb_base = mode->fb_base;
+
+      if ((!first_gop || conout_found) &&
+        info->pixel_format != GRUB_EFI_GOT_BLT_ONLY)
+        {
+    /*
+    * Systems that use the UEFI Console Splitter may
+    * provide multiple GOP devices, not all of which are
+    * backed by real hardware. The workaround is to search
+    * for a GOP implementing the ConOut protocol, and if
+    * one isn't found, to just fall back to the first GOP.
+    */
+    width = info->width;
+    height = info->height;
+    pixel_format = info->pixel_format;
+    pixel_info = info->pixel_bitmask;
+    pixels_per_scan_line = info->pixels_per_scanline;
+    fb_base = current_fb_base;
+
+    /*
+    * Once we've found a GOP supporting ConOut,
+    * don't bother looking any further.
+    */
+    first_gop = gop;
+    if (conout_found)
+      break;
+        }
+    }
+
+  /* Did we find any GOPs? */
+  if (!first_gop)
+    goto free_screen_info;
+
+  /* EFI framebuffer */
+  si->orig_video_isVGA = GRUB_VIDEO_TYPE_EFI;
+
+  si->lfb_width = width;
+  si->lfb_height = height;
+  si->lfb_base = fb_base;
+  grub_dprintf ("loongson", "Screen info fb base: 0x%"PRIxGRUB_UINT32_T"\n",
+                si->lfb_base);
+
+  ext_lfb_base = (grub_uint64_t)fb_base >> 32;
+  if (ext_lfb_base) {
+    si->capabilities |= GRUB_VIDEO_CAPABILITY_64BIT_BASE;
+    si->ext_lfb_base = ext_lfb_base;
+  }
+  si->pages = 1;
+
+  setup_pixel_info(si, pixels_per_scan_line, pixel_info, pixel_format);
+
+  si->lfb_size = si->lfb_linelength * si->lfb_height;
+  si->capabilities |= GRUB_VIDEO_CAPABILITY_SKIP_QUIRKS;
+
+  return si;
+
+free_screen_info:
+  b = grub_efi_system_table->boot_services;
+  b->install_configuration_table (&screen_info_guid, NULL);
+  if (si)
+    efi_call_1 (b->free_pool, si);
+
+  grub_dprintf ("loongson", "No screen info\n");
+  return NULL;
+}
+
 static grub_err_t
 allocate_fdt_and_exit_boot (struct linux_loongarch64_kernel_params *kernel_params)
 {
@@ -242,6 +472,8 @@ grub_linux_loongarch_elf_linux_boot_image (struct linux_loongarch64_kernel_param
   struct grub_relocator64_state state;
   grub_err_t err;
 
+  setup_screen_info ();
+
   /* linux kernel type is ELF */
   grub_memset (&state, 0, sizeof (state));
 
diff --git a/include/grub/loongarch64/linux.h b/include/grub/loongarch64/linux.h
index f4b198a..c010982 100644
--- a/include/grub/loongarch64/linux.h
+++ b/include/grub/loongarch64/linux.h
@@ -79,6 +79,53 @@ struct linux_loongarch64_kernel_params
                              sizeof (FDT_ADDR_CELLS_STRING) + \
                              sizeof (FDT_SIZE_CELLS_STRING))
 
+/*
+ * These are set up by the setup-routine at boot-time:
+ */
+struct screen_info {
+  grub_efi_uint8_t  orig_x;       /* 0x00 */
+  grub_efi_uint8_t  orig_y;       /* 0x01 */
+  grub_efi_uint16_t ext_mem_k;    /* 0x02 */
+  grub_efi_uint16_t orig_video_page;  /* 0x04 */
+  grub_efi_uint8_t  orig_video_mode;  /* 0x06 */
+  grub_efi_uint8_t  orig_video_cols;  /* 0x07 */
+  grub_efi_uint8_t  flags;        /* 0x08 */
+  grub_efi_uint8_t  unused2;      /* 0x09 */
+  grub_efi_uint16_t orig_video_ega_bx;/* 0x0a */
+  grub_efi_uint16_t unused3;      /* 0x0c */
+  grub_efi_uint8_t  orig_video_lines; /* 0x0e */
+  grub_efi_uint8_t  orig_video_isVGA; /* 0x0f */
+  grub_efi_uint16_t orig_video_points;/* 0x10 */
+
+  /* VESA graphic mode -- linear frame buffer */
+  grub_efi_uint16_t lfb_width;    /* 0x12 */
+  grub_efi_uint16_t lfb_height;   /* 0x14 */
+  grub_efi_uint16_t lfb_depth;    /* 0x16 */
+  grub_efi_uint32_t lfb_base;     /* 0x18 */
+  grub_efi_uint32_t lfb_size;     /* 0x1c */
+  grub_efi_uint16_t cl_magic, cl_offset; /* 0x20 */
+  grub_efi_uint16_t lfb_linelength;   /* 0x24 */
+  grub_efi_uint8_t  red_size;     /* 0x26 */
+  grub_efi_uint8_t  red_pos;      /* 0x27 */
+  grub_efi_uint8_t  green_size;   /* 0x28 */
+  grub_efi_uint8_t  green_pos;    /* 0x29 */
+  grub_efi_uint8_t  blue_size;    /* 0x2a */
+  grub_efi_uint8_t  blue_pos;     /* 0x2b */
+  grub_efi_uint8_t  rsvd_size;    /* 0x2c */
+  grub_efi_uint8_t  rsvd_pos;     /* 0x2d */
+  grub_efi_uint16_t vesapm_seg;   /* 0x2e */
+  grub_efi_uint16_t vesapm_off;   /* 0x30 */
+  grub_efi_uint16_t pages;        /* 0x32 */
+  grub_efi_uint16_t vesa_attributes;  /* 0x34 */
+  grub_efi_uint32_t capabilities;     /* 0x36 */
+  grub_efi_uint32_t ext_lfb_base; /* 0x3a */
+  grub_efi_uint8_t  _reserved[2]; /* 0x3e */
+} __attribute__((packed));
+
+#define GRUB_VIDEO_TYPE_EFI     0x70
+#define GRUB_VIDEO_CAPABILITY_SKIP_QUIRKS    (1 << 0)
+#define GRUB_VIDEO_CAPABILITY_64BIT_BASE     (1 << 1)    /* Frame buffer base is 64-bit */
+
 /* From arch/loongarch/include/asm/mach-loongson64/boot_param.h */
 struct _extention_list_hdr {
     grub_uint64_t		signature;
-- 
2.33.0

