text: /*
   *  GRUB  --  GRand Unified Bootloader
   *  Copyright (C) 2009  Free Software Foundation, Inc.
   *
   *  GRUB is free software: you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation, either version 3 of the License, or
   *  (at your option) any later version.
   *
   *  GRUB is distributed in the hope that it will be useful,
   *  but WITHOUT 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
   *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  /* This is an emulation of EFI runtime services.
     This allows a more uniform boot on i386 machines.
     As it emulates only runtime service it isn't able
     to chainload EFI bootloader on non-EFI system. */
  
  
  #include <grub/file.h>
  #include <grub/err.h>
  #include <grub/normal.h>
  #include <grub/mm.h>
  #include <grub/dl.h>
  #include <grub/misc.h>
  #include <grub/efiemu/efiemu.h>
  #include <grub/command.h>
  #include <grub/i18n.h>
  
  GRUB_MOD_LICENSE ("GPLv3+");
  
  /* System table. Two version depending on mode */
  grub_efi_system_table32_t *grub_efiemu_system_table32 = 0;
  grub_efi_system_table64_t *grub_efiemu_system_table64 = 0;
  /* Modules may need to execute some actions after memory allocation happens */
  static struct grub_efiemu_prepare_hook *efiemu_prepare_hooks = 0;
  /* Linked list of configuration tables */
  static struct grub_efiemu_configuration_table *efiemu_config_tables = 0;
  static int prepared = 0;
  
  /* Free all allocated space */
  grub_err_t
  grub_efiemu_unload (void)
  {
    struct grub_efiemu_configuration_table *cur, *d;
    struct grub_efiemu_prepare_hook *curhook, *d2;
    grub_efiemu_loadcore_unload ();
  
    grub_efiemu_mm_unload ();
  
    for (cur = efiemu_config_tables; cur;)
      {
        d = cur->next;
        if (cur->unload)
  	cur->unload (cur->data);
        grub_free (cur);
        cur = d;
      }
    efiemu_config_tables = 0;
  
    for (curhook = efiemu_prepare_hooks; curhook;)
      {
        d2 = curhook->next;
        if (curhook->unload)
  	curhook->unload (curhook->data);
        grub_free (curhook);
        curhook = d2;
      }
    efiemu_prepare_hooks = 0;
  
    prepared = 0;
  
    return GRUB_ERR_NONE;
  }
  
  /* Remove previously registered table from the list */
  grub_err_t
  grub_efiemu_unregister_configuration_table (grub_efi_guid_t guid)
  {
    struct grub_efiemu_configuration_table *cur, *prev;
  
    /* Special treating if head is to remove */
    while (efiemu_config_tables
  	 && !grub_memcmp (&(efiemu_config_tables->guid), &guid, sizeof (guid)))
      {
        if (efiemu_config_tables->unload)
  	  efiemu_config_tables->unload (efiemu_config_tables->data);
  	cur = efiemu_config_tables->next;
  	grub_free (efiemu_config_tables);
  	efiemu_config_tables = cur;
      }
    if (!efiemu_config_tables)
      return GRUB_ERR_NONE;
  
    /* Remove from chain */
    for (prev = efiemu_config_tables, cur = prev->next; cur;)
      if (grub_memcmp (&(cur->guid), &guid, sizeof (guid)) == 0)
        {
  	if (cur->unload)
  	  cur->unload (cur->data);
  	prev->next = cur->next;
  	grub_free (cur);
  	cur = prev->next;
        }
      else
        {
  	prev = cur;
  	cur = cur->next;
        }
    return GRUB_ERR_NONE;
  }
  
  grub_err_t
  grub_efiemu_register_prepare_hook (grub_err_t (*hook) (void *data),
  				   void (*unload) (void *data),
  				   void *data)
  {
    struct grub_efiemu_prepare_hook *nhook;
    nhook = (struct grub_efiemu_prepare_hook *) grub_malloc (sizeof (*nhook));
    if (! nhook)
      return grub_errno;
    nhook->hook = hook;
    nhook->unload = unload;
    nhook->data = data;
    nhook->next = efiemu_prepare_hooks;
    efiemu_prepare_hooks = nhook;
    return GRUB_ERR_NONE;
  }
  
  /* Register a configuration table either supplying the address directly
     or with a hook
  */
  grub_err_t
  grub_efiemu_register_configuration_table (grub_efi_guid_t guid,
  					  void * (*get_table) (void *data),
  					  void (*unload) (void *data),
  					  void *data)
  {
    struct grub_efiemu_configuration_table *tbl;
    grub_err_t err;
  
   err = grub_efiemu_unregister_configuration_table (guid);
    if (err)
      return err;
  
    tbl = (struct grub_efiemu_configuration_table *) grub_malloc (sizeof (*tbl));
    if (! tbl)
      return grub_errno;
  
    tbl->guid = guid;
    tbl->get_table = get_table;
    tbl->unload = unload;
    tbl->data = data;
    tbl->next = efiemu_config_tables;
    efiemu_config_tables = tbl;
  
    return GRUB_ERR_NONE;
  }
  
  static grub_err_t
  grub_cmd_efiemu_unload (grub_command_t cmd __attribute__ ((unused)),
  			int argc __attribute__ ((unused)),
  			char *args[] __attribute__ ((unused)))
  {
    return grub_efiemu_unload ();
  }
  
  static grub_err_t
  grub_cmd_efiemu_prepare (grub_command_t cmd __attribute__ ((unused)),
  			 int argc __attribute__ ((unused)),
  			 char *args[] __attribute__ ((unused)))
  {
    return grub_efiemu_prepare ();
  }
  
  
  
  /* Load the runtime from the file FILENAME.  */
  static grub_err_t
  grub_efiemu_load_file (const char *filename)
  {
    grub_file_t file;
    grub_err_t err;
  
    file = grub_file_open (filename, GRUB_FILE_TYPE_GRUB_MODULE);
    if (! file)
      return grub_errno;
  
    err = grub_efiemu_mm_init ();
    if (err)
      {
        grub_file_close (file);
        grub_efiemu_unload ();
        return err;
      }
  
    grub_dprintf ("efiemu", "mm initialized\n");
  
    err = grub_efiemu_loadcore_init (file, filename);
    if (err)
      {
        grub_file_close (file);
        grub_efiemu_unload ();
        return err;
      }
  
    grub_file_close (file);
  
    /* For configuration tables entry in system table. */
    grub_efiemu_request_symbols (1);
  
    return GRUB_ERR_NONE;
  }
  
  grub_err_t
  grub_efiemu_autocore (void)
  {
    const char *prefix;
    char *filename;
    const char *suffix;
    grub_err_t err;
  
    if (grub_efiemu_sizeof_uintn_t () != 0)
      return GRUB_ERR_NONE;
  
    prefix = grub_env_get ("prefix");
  
    if (! prefix)
      return grub_error (GRUB_ERR_FILE_NOT_FOUND,
  		       N_("variable `%s' isn't set"), "prefix");
  
    suffix = grub_efiemu_get_default_core_name ();
  
    filename = grub_xasprintf ("%s/" GRUB_TARGET_CPU "-" GRUB_PLATFORM "/%s",
  			     prefix, suffix);
    if (! filename)
      return grub_errno;
  
    err = grub_efiemu_load_file (filename);
    grub_free (filename);
    if (err)
      return err;
  #ifndef GRUB_MACHINE_EMU
    err = grub_machine_efiemu_init_tables ();
    if (err)
      return err;
  #endif
  
    return GRUB_ERR_NONE;
  }
  
  grub_err_t
  grub_efiemu_prepare (void)
  {
    grub_err_t err;
  
    if (prepared)
      return GRUB_ERR_NONE;
  
    err = grub_efiemu_autocore ();
    if (err)
      return err;
  
    grub_dprintf ("efiemu", "Preparing %d-bit efiemu\n",
  		8 * grub_efiemu_sizeof_uintn_t ());
  
    /* Create NVRAM. */
    grub_efiemu_pnvram ();
  
    prepared = 1;
  
    if (grub_efiemu_sizeof_uintn_t () == 4)
      return grub_efiemu_prepare32 (efiemu_prepare_hooks, efiemu_config_tables);
    else
      return grub_efiemu_prepare64 (efiemu_prepare_hooks, efiemu_config_tables);
  }
  
  
  static grub_err_t
  grub_cmd_efiemu_load (grub_command_t cmd __attribute__ ((unused)),
  		      int argc, char *args[])
  {
    grub_err_t err;
  
    grub_efiemu_unload ();
  
    if (argc != 1)
      return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
  
    err = grub_efiemu_load_file (args[0]);
    if (err)
      return err;
  #ifndef GRUB_MACHINE_EMU
    err = grub_machine_efiemu_init_tables ();
    if (err)
      return err;
  #endif
    return GRUB_ERR_NONE;
  }
  
  static grub_command_t cmd_loadcore, cmd_prepare, cmd_unload;
  
  GRUB_MOD_INIT(efiemu)
  {
    cmd_loadcore = grub_register_command ("efiemu_loadcore",
  					grub_cmd_efiemu_load,
  					N_("FILE"),
  					N_("Load and initialize EFI emulator."));
    cmd_prepare = grub_register_command ("efiemu_prepare",
  				       grub_cmd_efiemu_prepare,
  				       0,
  				       N_("Finalize loading of EFI emulator."));
    cmd_unload = grub_register_command ("efiemu_unload", grub_cmd_efiemu_unload,
  				      0,
  				      N_("Unload EFI emulator."));
  }
  
  GRUB_MOD_FINI(efiemu)
  {
    grub_unregister_command (cmd_loadcore);
    grub_unregister_command (cmd_prepare);
    grub_unregister_command (cmd_unload);
  }
  