From d984a64ff93443630fde3211e1a8ec42d92a6b56 Mon Sep 17 00:00:00 2001
From: mengyingkun <mengyingkun@loongson.cn>
Date: Mon, 13 Feb 2023 14:40:16 +0800
Subject: [PATCH] loongarch: Add support for v4.0 interface

This patch adds support for parameter passing converntion
between bootloader and kernel, defined in the document
"loongson devsys firmware kernel interface specification v4.0"

Signed-off-by: yangqiming <yangqiming@loongson.cn>
Signed-off-by: mengyingkun <mengyingkun@loongson.cn>
---
 grub-core/lib/loongarch64/relocator.c    |   2 +-
 grub-core/loader/loongarch64/linux-elf.c | 177 ++++++++++++++++++++---
 grub-core/loader/loongarch64/linux.c     |  49 ++-----
 include/grub/loongarch64/linux.h         |  20 +++
 4 files changed, 190 insertions(+), 58 deletions(-)

diff --git a/grub-core/lib/loongarch64/relocator.c b/grub-core/lib/loongarch64/relocator.c
index faa4553..587fc58 100644
--- a/grub-core/lib/loongarch64/relocator.c
+++ b/grub-core/lib/loongarch64/relocator.c
@@ -122,7 +122,7 @@ grub_relocator64_boot (struct grub_relocator *rel,
   unsigned i;
   grub_addr_t vtarget;
 
-  err = grub_relocator_alloc_chunk_align (rel, &ch, 0,
+  err = grub_relocator_alloc_chunk_align (rel, &ch, 0x3000000,
 					  (0xffffffff - stateset_size)
 					  + 1, stateset_size,
 					  grub_relocator_align,
diff --git a/grub-core/loader/loongarch64/linux-elf.c b/grub-core/loader/loongarch64/linux-elf.c
index 852e8f4..15f9984 100644
--- a/grub-core/loader/loongarch64/linux-elf.c
+++ b/grub-core/loader/loongarch64/linux-elf.c
@@ -25,11 +25,14 @@
 #include <grub/efi/memory.h>
 #include <grub/efi/graphics_output.h>
 
+#define GRUB_EFI_MMAP_NR_SLACK_SLOTS 8
+
 #define GRUB_ADDRESS_TYPE_SYSRAM	1
 #define GRUB_ADDRESS_TYPE_RESERVED	2
 #define GRUB_ADDRESS_TYPE_ACPI		3
 #define GRUB_ADDRESS_TYPE_NVS		4
 #define GRUB_ADDRESS_TYPE_PMEM		5
+
 #define GRUB_EFI_LOONGSON_BPI_TABLE_GUID	\
     { 0x4660f721, 0x2ec5, 0x416a, \
 	{ 0x89, 0x9a, 0x43, 0x18, 0x02, 0x50, 0xa0, 0xc9 } \
@@ -45,6 +48,16 @@
     { 0x9a, 0x46, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
   }
 
+#define GRUB_EFI_LARCH_BOOT_MEMMAP_GUID \
+  { 0x800f683f, 0xd08b, 0x423a,  \
+    { 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4 } \
+  }
+
+#define GRUB_EFI_LARCH_INITRD_MEDIA_GUID \
+  { 0x5568e427, 0x68fc, 0x4f3d,  \
+    { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68 } \
+  }
+
 static struct grub_relocator *relocator;
 static grub_efi_guid_t screen_info_guid = GRUB_EFI_LARCH_SCREEN_INFO_GUID;
 
@@ -271,6 +284,135 @@ free_screen_info:
   return NULL;
 }
 
+static grub_err_t
+allocate_memmap_and_exit_boot (struct linux_loongarch64_kernel_params *kernel_params)
+{
+  grub_err_t err;
+  grub_efi_status_t status;
+  grub_efi_uintn_t mmap_size, desc_size, size;
+  grub_efi_uint32_t desc_version;
+  grub_efi_memory_descriptor_t *mmap_buf;
+  grub_efi_boot_services_t *b;
+  struct efi_boot_memmap *m, tmp;
+  struct efi_initrd *tbl = NULL;
+  grub_efi_guid_t boot_memmap_guid = GRUB_EFI_LARCH_BOOT_MEMMAP_GUID;
+  grub_efi_guid_t initrd_media_guid = GRUB_EFI_LARCH_INITRD_MEDIA_GUID;
+
+  setup_screen_info();
+
+  b = grub_efi_system_table->boot_services;
+
+  grub_dprintf ("loongson", "ramdisk_addr:0x%"PRIxGRUB_UINT64_T", \
+          size:0x%"PRIxGRUB_UINT64_T"\n",
+          kernel_params->ramdisk_addr,
+          kernel_params->ramdisk_size);
+#if 0
+  char string[64];
+
+  /* Set initrd info to cmdline*/
+  if (kernel_params->ramdisk_addr && kernel_params->ramdisk_size)
+  {
+    grub_printf ( "Initrd @ %p-%p\n",
+            (void *) kernel_params->ramdisk_addr,
+            (void *) (kernel_params->ramdisk_addr + kernel_params->ramdisk_size));
+    /* initrd */
+    grub_snprintf (string,
+         sizeof (GRUB_INITRD_STRING),
+         "initrd=0x%lx,0x%lx",
+         ((grub_uint64_t) kernel_params->ramdisk_addr & 0xffffffff),
+         (grub_uint64_t) kernel_params->ramdisk_size);
+    *(char*) ((grub_addr_t) kernel_params->linux_args + kernel_params->ramdisk_args_len - 2) = ' ';
+    grub_memcpy ((char*) ((grub_addr_t) kernel_params->linux_args + kernel_params->ramdisk_args_len - 1),
+            string, sizeof (GRUB_INITRD_STRING));
+  }
+#else
+  /* Set initrd info to system table*/
+  if (kernel_params->ramdisk_addr && kernel_params->ramdisk_size)
+  {
+    tbl = grub_efi_allocate_any_pages (GRUB_EFI_BYTES_TO_PAGES (sizeof(struct efi_initrd)));
+    if (!tbl)
+      return grub_error (GRUB_ERR_IO, "cannot allocate tbl memory");
+    tbl->base = kernel_params->ramdisk_addr;
+    tbl->size = kernel_params->ramdisk_size;
+
+    status = b->install_configuration_table (&initrd_media_guid, tbl);
+    if (status != GRUB_EFI_SUCCESS) {
+      grub_error (GRUB_ERR_IO, "failed to install initrd media");
+      goto free_tbl;
+    }
+  }
+#endif
+
+  tmp.map_size = 0;
+  status = grub_efi_get_memory_map (&tmp.map_size, NULL, &tmp.map_key,
+                    &tmp.desc_size, &tmp.desc_ver);
+  if (status != 0) {
+    grub_error (GRUB_ERR_IO, "cannot get memory map");
+    goto uninstall_initrd_table;
+  }
+  size = tmp.map_size + tmp.desc_size * GRUB_EFI_MMAP_NR_SLACK_SLOTS;
+  m = grub_efi_allocate_any_pages (GRUB_EFI_BYTES_TO_PAGES (sizeof(*m) + size));
+  if (!m) {
+    grub_error (GRUB_ERR_IO, "cannot allocate m memory");
+    goto uninstall_initrd_table;
+  }
+
+  status = b->install_configuration_table (&boot_memmap_guid, m);
+  if (status != GRUB_EFI_SUCCESS) {
+    grub_error (GRUB_ERR_IO, "failed to install boot memmap");
+    goto free_m;
+  }
+
+  m->buff_size = m->map_size = size;
+  if (grub_efi_get_memory_map (&m->map_size, m->map,
+                   &m->map_key, &m->desc_size,
+                   &m->desc_ver) <= 0)
+    {
+      grub_error (GRUB_ERR_IO, "cannot get EFI memory map");
+      goto uninstall_mem_table;
+    }
+
+  mmap_size = grub_efi_find_mmap_size ();
+  if (! mmap_size)
+    goto uninstall_mem_table;
+
+  mmap_buf = grub_efi_allocate_any_pages (GRUB_EFI_BYTES_TO_PAGES (mmap_size));
+  err = grub_efi_finish_boot_services (&mmap_size, mmap_buf, NULL,
+                                         &desc_size, &desc_version);
+  if (err) {
+    grub_error (GRUB_ERR_IO, "failed to finish boot services");
+    goto free_map;
+  }
+
+  return 0;
+
+free_map:
+  if (mmap_buf)
+    grub_efi_free_pages ((grub_addr_t) mmap_buf,
+               GRUB_EFI_BYTES_TO_PAGES (mmap_size));
+
+uninstall_mem_table:
+  b->install_configuration_table (&boot_memmap_guid, NULL);
+
+free_m:
+  if (m)
+    grub_efi_free_pages ((grub_addr_t) m,
+               GRUB_EFI_BYTES_TO_PAGES (sizeof(*m) + size));
+
+uninstall_initrd_table:
+  if (kernel_params->ramdisk_addr && kernel_params->ramdisk_size)
+    b->install_configuration_table (&initrd_media_guid, NULL);
+
+free_tbl:
+  if (kernel_params->ramdisk_addr && kernel_params->ramdisk_size) {
+    if (tbl)
+      grub_efi_free_pages ((grub_addr_t) tbl,
+               GRUB_EFI_BYTES_TO_PAGES (sizeof(struct efi_initrd)));
+  }
+
+  return grub_error(GRUB_ERR_BAD_OS, "failed to V40 boot");
+}
+
 static grub_err_t
 allocate_fdt_and_exit_boot (struct linux_loongarch64_kernel_params *kernel_params)
 {
@@ -400,12 +542,11 @@ grub_linux_loongarch_elf_make_argv (struct linux_loongarch64_kernel_params *kern
     }
 
   if (kernel_params->ramdisk_addr && kernel_params->ramdisk_size)
-  {
-    size += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4) \
-	  + ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4) \
-	  + ALIGN_UP (sizeof ("initrd=0xXXXXXXXXXXXXXXXX,0xXXXXXXXXXXXXXXXX"),
-		      4);
-  }
+    {
+      size += ALIGN_UP (sizeof (GRUB_RD_START_STRING), 4)
+	            + ALIGN_UP (sizeof (GRUB_RD_SIZE_STRING), 4)
+	            + ALIGN_UP (sizeof (GRUB_INITRD_STRING), 4);
+    }
   size = ALIGN_UP (size, 8);
 
   /* alloc memory */
@@ -427,33 +568,33 @@ grub_linux_loongarch_elf_make_argv (struct linux_loongarch64_kernel_params *kern
   {
     /* rd_start */
     grub_snprintf (linux_args,
-		 sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"),
+		 sizeof (GRUB_RD_START_STRING),
 		 "rd_start=0x%lx",
 		 (grub_uint64_t) kernel_params->ramdisk_addr);
     *linux_argv = (grub_uint64_t) (grub_addr_t) linux_args;
     linux_argv++;
-    linux_args += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4);
+    linux_args += ALIGN_UP (sizeof (GRUB_RD_START_STRING), 4);
     kernel_params->linux_argc++;
 
     /* rd_size */
     grub_snprintf (linux_args,
-		 sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"),
+		 sizeof (GRUB_RD_SIZE_STRING),
 		 "rd_size=0x%lx",
 		 (grub_uint64_t) kernel_params->ramdisk_size);
     *linux_argv = (grub_uint64_t) (grub_addr_t) linux_args;
     linux_argv++;
-    linux_args += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4);
+    linux_args += ALIGN_UP (sizeof (GRUB_RD_SIZE_STRING), 4);
     kernel_params->linux_argc++;
 
     /* initrd */
     grub_snprintf (linux_args,
-		 sizeof ("initrd=0xXXXXXXXXXXXXXXXX,0xXXXXXXXXXXXXXXXX"),
+		 sizeof (GRUB_INITRD_STRING),
 		 "initrd=0x%lx,0x%lx",
 		 ((grub_uint64_t) kernel_params->ramdisk_addr & 0xffffffff),
 		 (grub_uint64_t) kernel_params->ramdisk_size);
     *linux_argv = (grub_uint64_t) (grub_addr_t) linux_args;
     linux_argv++;
-    linux_args += ALIGN_UP (sizeof ("initrd=0xXXXXXXXXXXXXXXXX,0xXXXXXXXXXXXXXXXX"), 4);
+    linux_args += ALIGN_UP (sizeof (GRUB_INITRD_STRING), 4);
     kernel_params->linux_argc++;
   }
 
@@ -472,8 +613,6 @@ 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));
 
@@ -481,14 +620,14 @@ grub_linux_loongarch_elf_linux_boot_image (struct linux_loongarch64_kernel_param
   state.gpr[1] = kernel_params->kernel_addr;  /* ra */
   if (grub_linux_loongarch_elf_get_boot_params (&boot_params) == 0)
     {
-      grub_printf("not find param, is fdt boot\n");
-      if (allocate_fdt_and_exit_boot (kernel_params) != GRUB_ERR_NONE)
+      grub_dprintf("loongson", "V4.0 boot\n");
+      if (allocate_memmap_and_exit_boot (kernel_params) != GRUB_ERR_NONE)
         return grub_errno;
       state.gpr[4] = 1 << FLAGS_EFI_SUPPORT_BIT;   /* a0 = flag */
-      state.gpr[5] = (grub_uint64_t)kernel_params->fdt; /* a1 = fdt */
-      state.gpr[6] = 0; /* a2 = flag */
+      state.gpr[5] = (grub_uint64_t)kernel_params->linux_args; /* a1 = cmdline */
+      state.gpr[6] = (grub_uint64_t)grub_efi_system_table; /* a2 = system_table */
     } else {
-      grub_printf("find param, is bpi boot\n");
+      grub_dprintf("loongson", "BPI boot\n");
       grub_linux_loongarch_elf_make_argv (kernel_params);
       state.gpr[4] = kernel_params->linux_argc;   /* a0 = argc */
       state.gpr[5] = kernel_params->linux_argv; /* a1 = args */
diff --git a/grub-core/loader/loongarch64/linux.c b/grub-core/loader/loongarch64/linux.c
index 4c02194..03b7940 100644
--- a/grub-core/loader/loongarch64/linux.c
+++ b/grub-core/loader/loongarch64/linux.c
@@ -230,7 +230,10 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
       grub_dprintf ("linux", "kernel @ %p\n", (void*) kernel_params.kernel_addr);
     }
 
-  cmdline_size = grub_loader_cmdline_size (argc, argv) + sizeof (LINUX_IMAGE);
+  cmdline_size = grub_loader_cmdline_size (argc, argv) + sizeof (LINUX_IMAGE)
+                 + sizeof (GRUB_INITRD_STRING);
+  kernel_params.ramdisk_args_len = grub_loader_cmdline_size (argc, argv)
+                                   + sizeof (LINUX_IMAGE);
   kernel_params.linux_argc = argc;
   kernel_params.linux_args = grub_malloc (cmdline_size);
   if (!kernel_params.linux_args)
@@ -250,7 +253,7 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
     {
       err = grub_create_loader_cmdline (argc, argv,
 					(char*) ((grub_addr_t) kernel_params.linux_args + sizeof (LINUX_IMAGE) - 1),
-					cmdline_size,
+					kernel_params.ramdisk_args_len,
 					GRUB_VERIFY_KERNEL_CMDLINE);
       if (err)
 	goto fail;
@@ -299,26 +302,6 @@ fail:
   return grub_errno;
 }
 
-/*
- * This function returns a pointer to a legally allocated initrd buffer,
- * or NULL if unsuccessful
- */
-static void *
-allocate_initrd_mem (int initrd_pages)
-{
-  grub_addr_t max_addr;
-
-  if (grub_efi_get_ram_base (&max_addr) != GRUB_ERR_NONE)
-    return NULL;
-
-  max_addr += INITRD_MAX_ADDRESS_OFFSET - 1;
-
-  return grub_efi_allocate_pages_real (max_addr, initrd_pages,
-                                       GRUB_EFI_ALLOCATE_MAX_ADDRESS,
-                                       GRUB_EFI_LOADER_DATA);
-}
-
-
 static grub_err_t
 grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
 		 int argc, char *argv[])
@@ -326,6 +309,7 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
   struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
   grub_size_t initrd_size;
   void *initrd_mem = NULL;
+  grub_err_t err;
 
   if (argc == 0)
     {
@@ -345,16 +329,9 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
   initrd_size = grub_get_initrd_size (&initrd_ctx);
   grub_dprintf ("linux", "Loading initrd\n");
 
-  if (is_bpi_boot == 0) {
-      grub_size_t initrd_pages;
-      initrd_pages = (GRUB_EFI_BYTES_TO_PAGES (initrd_size));
-      initrd_mem = allocate_initrd_mem (initrd_pages);
-  } else {
-      grub_err_t err;
-      initrd_mem = grub_linux_loongarch_alloc_initrd_mem_align (initrd_size, 0x10000, &err);
-      if (err)
-	goto fail;
-  }
+  initrd_mem = grub_linux_loongarch_alloc_initrd_mem_align (initrd_size, 0x1000, &err);
+  if (err)
+    goto fail;
 
   if (!initrd_mem)
     {
@@ -369,14 +346,10 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
   kernel_params.ramdisk_addr = (grub_addr_t) initrd_mem;
   kernel_params.ramdisk_size = initrd_size;
   grub_dprintf ("linux", "ramdisk [addr=%p, size=0x%lx]\n",
-		(void *) initrd_mem, initrd_size);
+                 (void *) initrd_mem, initrd_size);
 fail:
   grub_initrd_close (&initrd_ctx);
-  if (is_bpi_boot == 0) {
-      if (initrd_mem && !kernel_params.ramdisk_addr)
-	grub_efi_free_pages ((grub_addr_t) initrd_mem,
-			     GRUB_EFI_BYTES_TO_PAGES (initrd_size));
-  }
+
   return grub_errno;
 }
 
diff --git a/include/grub/loongarch64/linux.h b/include/grub/loongarch64/linux.h
index c010982..f20a719 100644
--- a/include/grub/loongarch64/linux.h
+++ b/include/grub/loongarch64/linux.h
@@ -59,6 +59,7 @@ struct linux_loongarch64_kernel_params
   grub_size_t kernel_size;	/* kernel size */
   grub_addr_t ramdisk_addr;	/* initrd load address */
   grub_size_t ramdisk_size;	/* initrd size */
+  int         ramdisk_args_len;  /* position of initrd in linux_args */
   int         linux_argc;	/* cmdline parameters number*/
   grub_addr_t linux_argv;	/* cmdline parameters address*/
   void*       linux_args;
@@ -73,12 +74,31 @@ struct linux_loongarch64_kernel_params
 #define ELF64_LOADMASK (0xf000000000000000ULL)
 #define FLAGS_EFI_SUPPORT_BIT 0
 
+/*initrd info*/
+#define GRUB_RD_START_STRING "rd_start=0xXXXXXXXXXXXXXXXX"
+#define GRUB_RD_SIZE_STRING  "rd_size=0xXXXXXXXXXXXXXXXX"
+#define GRUB_INITRD_STRING   "initrd=0xXXXXXXXXXXXXXXXX,0xXXXXXXXXXXXXXXXX"
+
 #define FDT_ADDR_CELLS_STRING "#address-cells"
 #define FDT_SIZE_CELLS_STRING "#size-cells"
 #define FDT_ADDR_SIZE_EXTRA ((2 * grub_fdt_prop_entry_size (sizeof(grub_uint32_t))) + \
                              sizeof (FDT_ADDR_CELLS_STRING) + \
                              sizeof (FDT_SIZE_CELLS_STRING))
 
+struct efi_boot_memmap {
+  grub_efi_uintn_t  map_size;
+  grub_efi_uintn_t  desc_size;
+  grub_efi_uint32_t desc_ver;
+  grub_efi_uintn_t  map_key;
+  grub_efi_uintn_t  buff_size;
+  grub_efi_memory_descriptor_t map[];
+};
+
+struct efi_initrd {
+  grub_efi_uintn_t  base;
+  grub_efi_uintn_t  size;
+};
+
 /*
  * These are set up by the setup-routine at boot-time:
  */
-- 
2.33.0

