/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2013 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); struct xen_loader_state { struct grub_relocator *relocator; struct grub_relocator_xen_state state; struct start_info next_start; struct grub_xen_file_info xen_inf; grub_xen_mfn_t *virt_mfn_list; struct start_info *virt_start_info; grub_xen_mfn_t console_pfn; grub_uint64_t max_addr; grub_uint64_t *virt_pgtable; grub_uint64_t pgtbl_start; grub_uint64_t pgtbl_end; struct xen_multiboot_mod_list *module_info_page; grub_uint64_t modules_target_start; grub_size_t n_modules; int loaded; }; static struct xen_loader_state xen_state; static grub_dl_t my_mod; #define PAGE_SIZE 4096 #define MAX_MODULES (PAGE_SIZE / sizeof (struct xen_multiboot_mod_list)) #define PAGE_SHIFT 12 #define STACK_SIZE 1048576 #define ADDITIONAL_SIZE (1 << 19) #define ALIGN_SIZE (1 << 22) #define LOG_POINTERS_PER_PAGE 9 #define POINTERS_PER_PAGE (1 << LOG_POINTERS_PER_PAGE) static grub_uint64_t page2offset (grub_uint64_t page) { return page << PAGE_SHIFT; } #ifdef __x86_64__ #define NUMBER_OF_LEVELS 4 #define INTERMEDIATE_OR 7 #else #define NUMBER_OF_LEVELS 3 #define INTERMEDIATE_OR 3 #endif static grub_uint64_t get_pgtable_size (grub_uint64_t total_pages, grub_uint64_t virt_base) { if (!virt_base) total_pages++; grub_uint64_t ret = 0; grub_uint64_t ll = total_pages; int i; for (i = 0; i < NUMBER_OF_LEVELS; i++) { ll = (ll + POINTERS_PER_PAGE - 1) >> LOG_POINTERS_PER_PAGE; /* PAE wants all 4 root directories present. */ #ifdef __i386__ if (i == 1) ll = 4; #endif ret += ll; } for (i = 1; i < NUMBER_OF_LEVELS; i++) if (virt_base >> (PAGE_SHIFT + i * LOG_POINTERS_PER_PAGE)) ret++; return ret; } static void generate_page_table (grub_uint64_t *where, grub_uint64_t paging_start, grub_uint64_t paging_end, grub_uint64_t total_pages, grub_uint64_t virt_base, grub_xen_mfn_t *mfn_list) { if (!virt_base) paging_end++; grub_uint64_t lx[NUMBER_OF_LEVELS], lxs[NUMBER_OF_LEVELS]; grub_uint64_t nlx, nls, sz = 0; int l; nlx = paging_end; nls = virt_base >> PAGE_SHIFT; for (l = 0; l < NUMBER_OF_LEVELS; l++) { nlx = (nlx + POINTERS_PER_PAGE - 1) >> LOG_POINTERS_PER_PAGE; /* PAE wants all 4 root directories present. */ #ifdef __i386__ if (l == 1) nlx = 4; #endif lx[l] = nlx; sz += lx[l]; lxs[l] = nls & (POINTERS_PER_PAGE - 1); if (nls && l != 0) sz++; nls >>= LOG_POINTERS_PER_PAGE; } grub_uint64_t lp; grub_uint64_t j; grub_uint64_t *pg = (grub_uint64_t *) where; int pr = 0; grub_memset (pg, 0, sz * PAGE_SIZE); lp = paging_start + lx[NUMBER_OF_LEVELS - 1]; for (l = NUMBER_OF_LEVELS - 1; l >= 1; l--) { if (lxs[l] || pr) pg[0] = page2offset (mfn_list[lp++]) | INTERMEDIATE_OR; if (pr) pg += POINTERS_PER_PAGE; for (j = 0; j < lx[l - 1]; j++) pg[j + lxs[l]] = page2offset (mfn_list[lp++]) | INTERMEDIATE_OR; pg += lx[l] * POINTERS_PER_PAGE; if (lxs[l]) pr = 1; } if (lxs[0] || pr) pg[0] = page2offset (mfn_list[total_pages]) | 5; if (pr) pg += POINTERS_PER_PAGE; for (j = 0; j < paging_end; j++) { if (j >= paging_start && j < lp) pg[j + lxs[0]] = page2offset (mfn_list[j]) | 5; else pg[j + lxs[0]] = page2offset (mfn_list[j]) | 7; } } static grub_err_t set_mfns (grub_xen_mfn_t pfn) { grub_xen_mfn_t i, t; grub_xen_mfn_t cn_pfn = -1, st_pfn = -1; struct mmu_update m2p_updates[4]; for (i = 0; i < grub_xen_start_page_addr->nr_pages; i++) { if (xen_state.virt_mfn_list[i] == grub_xen_start_page_addr->console.domU.mfn) cn_pfn = i; if (xen_state.virt_mfn_list[i] == grub_xen_start_page_addr->store_mfn) st_pfn = i; } if (cn_pfn == (grub_xen_mfn_t)-1) return grub_error (GRUB_ERR_BUG, "no console"); if (st_pfn == (grub_xen_mfn_t)-1) return grub_error (GRUB_ERR_BUG, "no store"); t = xen_state.virt_mfn_list[pfn]; xen_state.virt_mfn_list[pfn] = xen_state.virt_mfn_list[cn_pfn]; xen_state.virt_mfn_list[cn_pfn] = t; t = xen_state.virt_mfn_list[pfn + 1]; xen_state.virt_mfn_list[pfn + 1] = xen_state.virt_mfn_list[st_pfn]; xen_state.virt_mfn_list[st_pfn] = t; m2p_updates[0].ptr = page2offset (xen_state.virt_mfn_list[pfn]) | MMU_MACHPHYS_UPDATE; m2p_updates[0].val = pfn; m2p_updates[1].ptr = page2offset (xen_state.virt_mfn_list[pfn + 1]) | MMU_MACHPHYS_UPDATE; m2p_updates[1].val = pfn + 1; m2p_updates[2].ptr = page2offset (xen_state.virt_mfn_list[cn_pfn]) | MMU_MACHPHYS_UPDATE; m2p_updates[2].val = cn_pfn; m2p_updates[3].ptr = page2offset (xen_state.virt_mfn_list[st_pfn]) | MMU_MACHPHYS_UPDATE; m2p_updates[3].val = st_pfn; grub_xen_mmu_update (m2p_updates, 4, NULL, DOMID_SELF); return GRUB_ERR_NONE; } static grub_err_t grub_xen_p2m_alloc (void) { grub_relocator_chunk_t ch; grub_size_t p2msize; grub_err_t err; xen_state.state.mfn_list = xen_state.max_addr; xen_state.next_start.mfn_list = xen_state.max_addr + xen_state.xen_inf.virt_base; p2msize = sizeof (grub_xen_mfn_t) * grub_xen_start_page_addr->nr_pages; err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, xen_state.max_addr, p2msize); if (err) return err; xen_state.virt_mfn_list = get_virtual_current_address (ch); grub_memcpy (xen_state.virt_mfn_list, (void *) grub_xen_start_page_addr->mfn_list, p2msize); xen_state.max_addr = ALIGN_UP (xen_state.max_addr + p2msize, PAGE_SIZE); return GRUB_ERR_NONE; } static grub_err_t grub_xen_special_alloc (void) { grub_relocator_chunk_t ch; grub_err_t err; err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, xen_state.max_addr, sizeof (xen_state.next_start)); if (err) return err; xen_state.state.start_info = xen_state.max_addr + xen_state.xen_inf.virt_base; xen_state.virt_start_info = get_virtual_current_address (ch); xen_state.max_addr = ALIGN_UP (xen_state.max_addr + sizeof (xen_state.next_start), PAGE_SIZE); xen_state.console_pfn = xen_state.max_addr >> PAGE_SHIFT; xen_state.max_addr += 2 * PAGE_SIZE; xen_state.next_start.nr_pages = grub_xen_start_page_addr->nr_pages; grub_memcpy (xen_state.next_start.magic, grub_xen_start_page_addr->magic, sizeof (xen_state.next_start.magic)); xen_state.next_start.store_mfn = grub_xen_start_page_addr->store_mfn; xen_state.next_start.store_evtchn = grub_xen_start_page_addr->store_evtchn; xen_state.next_start.console.domU = grub_xen_start_page_addr->console.domU; xen_state.next_start.shared_info = grub_xen_start_page_addr->shared_info; return GRUB_ERR_NONE; } static grub_err_t grub_xen_pt_alloc (void) { grub_relocator_chunk_t ch; grub_err_t err; grub_uint64_t nr_info_pages; grub_uint64_t nr_pages, nr_pt_pages, nr_need_pages; xen_state.next_start.pt_base = xen_state.max_addr + xen_state.xen_inf.virt_base; xen_state.state.paging_start = xen_state.max_addr >> PAGE_SHIFT; nr_info_pages = xen_state.max_addr >> PAGE_SHIFT; nr_pages = nr_info_pages; while (1) { nr_pages = ALIGN_UP (nr_pages, (ALIGN_SIZE >> PAGE_SHIFT)); nr_pt_pages = get_pgtable_size (nr_pages, xen_state.xen_inf.virt_base); nr_need_pages = nr_info_pages + nr_pt_pages + ((ADDITIONAL_SIZE + STACK_SIZE) >> PAGE_SHIFT); if (nr_pages >= nr_need_pages) break; nr_pages = nr_need_pages; } err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, xen_state.max_addr, page2offset (nr_pt_pages)); if (err) return err; xen_state.virt_pgtable = get_virtual_current_address (ch); xen_state.pgtbl_start = xen_state.max_addr >> PAGE_SHIFT; xen_state.max_addr += page2offset (nr_pt_pages); xen_state.state.stack = xen_state.max_addr + STACK_SIZE + xen_state.xen_inf.virt_base; xen_state.state.paging_size = nr_pt_pages; xen_state.next_start.nr_pt_frames = nr_pt_pages; xen_state.max_addr = page2offset (nr_pages); xen_state.pgtbl_end = nr_pages; return GRUB_ERR_NONE; } static grub_err_t grub_xen_boot (void) { grub_err_t err; grub_uint64_t nr_pages; struct gnttab_set_version gnttab_setver; grub_size_t i; if (grub_xen_n_allocated_shared_pages) return grub_error (GRUB_ERR_BUG, "active grants"); err = grub_xen_p2m_alloc (); if (err) return err; err = grub_xen_special_alloc (); if (err) return err; err = grub_xen_pt_alloc (); if (err) return err; err = set_mfns (xen_state.console_pfn); if (err) return err; nr_pages = xen_state.max_addr >> PAGE_SHIFT; grub_dprintf ("xen", "bootstrap domain %llx+%llx\n", (unsigned long long) xen_state.xen_inf.virt_base, (unsigned long long) page2offset (nr_pages)); generate_page_table (xen_state.virt_pgtable, xen_state.pgtbl_start, xen_state.pgtbl_end, nr_pages, xen_state.xen_inf.virt_base, xen_state.virt_mfn_list); xen_state.state.entry_point = xen_state.xen_inf.entry_point; *xen_state.virt_start_info = xen_state.next_start; grub_memset (&gnttab_setver, 0, sizeof (gnttab_setver)); gnttab_setver.version = 1; grub_xen_grant_table_op (GNTTABOP_set_version, &gnttab_setver, 1); for (i = 0; i < ARRAY_SIZE (grub_xen_shared_info->evtchn_pending); i++) grub_xen_shared_info->evtchn_pending[i] = 0; return grub_relocator_xen_boot (xen_state.relocator, xen_state.state, nr_pages, xen_state.xen_inf.virt_base < PAGE_SIZE ? page2offset (nr_pages) : 0, xen_state.pgtbl_end - 1, page2offset (xen_state.pgtbl_end - 1) + xen_state.xen_inf.virt_base); } static void grub_xen_reset (void) { grub_relocator_unload (xen_state.relocator); grub_memset (&xen_state, 0, sizeof (xen_state)); } static grub_err_t grub_xen_unload (void) { grub_xen_reset (); grub_dl_unref (my_mod); return GRUB_ERR_NONE; } #define HYPERCALL_INTERFACE_SIZE 32 #ifdef __x86_64__ static grub_uint8_t template[] = { 0x51, /* push %rcx */ 0x41, 0x53, /* push %r11 */ 0x48, 0xc7, 0xc0, 0xbb, 0xaa, 0x00, 0x00, /* mov $0xaabb,%rax */ 0x0f, 0x05, /* syscall */ 0x41, 0x5b, /* pop %r11 */ 0x59, /* pop %rcx */ 0xc3 /* ret */ }; static grub_uint8_t template_iret[] = { 0x51, /* push %rcx */ 0x41, 0x53, /* push %r11 */ 0x50, /* push %rax */ 0x48, 0xc7, 0xc0, 0x17, 0x00, 0x00, 0x00, /* mov $0x17,%rax */ 0x0f, 0x05 /* syscall */ }; #define CALLNO_OFFSET 6 #else static grub_uint8_t template[] = { 0xb8, 0xbb, 0xaa, 0x00, 0x00, /* mov imm32, %eax */ 0xcd, 0x82, /* int $0x82 */ 0xc3 /* ret */ }; static grub_uint8_t template_iret[] = { 0x50, /* push %eax */ 0xb8, 0x17, 0x00, 0x00, 0x00, /* mov $0x17,%eax */ 0xcd, 0x82, /* int $0x82 */ }; #define CALLNO_OFFSET 1 #endif static void set_hypercall_interface (grub_uint8_t *tgt, unsigned callno) { if (callno == 0x17) { grub_memcpy (tgt, template_iret, ARRAY_SIZE (template_iret)); grub_memset (tgt + ARRAY_SIZE (template_iret), 0xcc, HYPERCALL_INTERFACE_SIZE - ARRAY_SIZE (template_iret)); return; } grub_memcpy (tgt, template, ARRAY_SIZE (template)); grub_memset (tgt + ARRAY_SIZE (template), 0xcc, HYPERCALL_INTERFACE_SIZE - ARRAY_SIZE (template)); tgt[CALLNO_OFFSET] = callno & 0xff; tgt[CALLNO_OFFSET + 1] = callno >> 8; } #ifdef __x86_64__ #define grub_elfXX_load grub_elf64_load #else #define grub_elfXX_load grub_elf32_load #endif static grub_err_t grub_cmd_xen (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_file_t file; grub_elf_t elf; grub_err_t err; void *kern_chunk_src; grub_relocator_chunk_t ch; grub_addr_t kern_start; grub_addr_t kern_end; if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); /* Call grub_loader_unset early to avoid it being called by grub_loader_set */ grub_loader_unset (); grub_xen_reset (); grub_create_loader_cmdline (argc - 1, argv + 1, (char *) xen_state.next_start.cmd_line, sizeof (xen_state.next_start.cmd_line) - 1); file = grub_file_open (argv[0]); if (!file) return grub_errno; elf = grub_xen_file (file); if (!elf) goto fail; err = grub_xen_get_info (elf, &xen_state.xen_inf); if (err) goto fail; #ifdef __x86_64__ if (xen_state.xen_inf.arch != GRUB_XEN_FILE_X86_64) #else if (xen_state.xen_inf.arch != GRUB_XEN_FILE_I386_PAE && xen_state.xen_inf.arch != GRUB_XEN_FILE_I386_PAE_BIMODE) #endif { grub_error (GRUB_ERR_BAD_OS, "incompatible architecture: %d", xen_state.xen_inf.arch); goto fail; } if (xen_state.xen_inf.virt_base & (PAGE_SIZE - 1)) { grub_error (GRUB_ERR_BAD_OS, "unaligned virt_base"); goto fail; } grub_dprintf ("xen", "virt_base = %llx, entry = %llx\n", (unsigned long long) xen_state.xen_inf.virt_base, (unsigned long long) xen_state.xen_inf.entry_point); xen_state.relocator = grub_relocator_new (); if (!xen_state.relocator) goto fail; kern_start = xen_state.xen_inf.kern_start - xen_state.xen_inf.paddr_offset; kern_end = xen_state.xen_inf.kern_end - xen_state.xen_inf.paddr_offset; if (xen_state.xen_inf.has_hypercall_page) { grub_dprintf ("xen", "hypercall page at 0x%llx\n", (unsigned long long) xen_state.xen_inf.hypercall_page); kern_start = grub_min (kern_start, xen_state.xen_inf.hypercall_page - xen_state.xen_inf.virt_base); kern_end = grub_max (kern_end, xen_state.xen_inf.hypercall_page - xen_state.xen_inf.virt_base + PAGE_SIZE); } xen_state.max_addr = ALIGN_UP (kern_end, PAGE_SIZE); err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, kern_start, kern_end - kern_start); if (err) goto fail; kern_chunk_src = get_virtual_current_address (ch); grub_dprintf ("xen", "paddr_offset = 0x%llx\n", (unsigned long long) xen_state.xen_inf.paddr_offset); grub_dprintf ("xen", "kern_start = 0x%llx, kern_end = 0x%llx\n", (unsigned long long) xen_state.xen_inf.kern_start, (unsigned long long) xen_state.xen_inf.kern_end); err = grub_elfXX_load (elf, argv[0], (grub_uint8_t *) kern_chunk_src - kern_start - xen_state.xen_inf.paddr_offset, 0, 0, 0); if (xen_state.xen_inf.has_hypercall_page) { unsigned i; for (i = 0; i < PAGE_SIZE / HYPERCALL_INTERFACE_SIZE; i++) set_hypercall_interface ((grub_uint8_t *) kern_chunk_src + i * HYPERCALL_INTERFACE_SIZE + xen_state.xen_inf.hypercall_page - xen_state.xen_inf.virt_base - kern_start, i); } if (err) goto fail; grub_dl_ref (my_mod); xen_state.loaded = 1; grub_loader_set (grub_xen_boot, grub_xen_unload, 0); goto fail; fail: /* grub_errno might be clobbered by further calls, save the error reason. */ err = grub_errno; if (elf) grub_elf_close (elf); else if (file) grub_file_close (file); if (err != GRUB_ERR_NONE) grub_xen_reset (); return err; } static grub_err_t grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_size_t size = 0; grub_err_t err; struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 }; grub_relocator_chunk_t ch; if (argc == 0) { grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); goto fail; } if (!xen_state.loaded) { grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first")); goto fail; } if (xen_state.next_start.mod_start || xen_state.next_start.mod_len) { grub_error (GRUB_ERR_BAD_ARGUMENT, N_("initrd already loaded")); goto fail; } if (grub_initrd_init (argc, argv, &initrd_ctx)) goto fail; size = grub_get_initrd_size (&initrd_ctx); if (size) { err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, xen_state.max_addr, size); if (err) goto fail; if (grub_initrd_load (&initrd_ctx, argv, get_virtual_current_address (ch))) goto fail; } xen_state.next_start.mod_start = xen_state.max_addr + xen_state.xen_inf.virt_base; xen_state.next_start.mod_len = size; xen_state.max_addr = ALIGN_UP (xen_state.max_addr + size, PAGE_SIZE); grub_dprintf ("xen", "Initrd, addr=0x%x, size=0x%x\n", (unsigned) xen_state.next_start.mod_start, (unsigned) size); fail: grub_initrd_close (&initrd_ctx); return grub_errno; } static grub_err_t grub_cmd_module (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_size_t size = 0; grub_err_t err; grub_relocator_chunk_t ch; grub_size_t cmdline_len; int nounzip = 0; grub_file_t file; if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); if (grub_strcmp (argv[0], "--nounzip") == 0) { argv++; argc--; nounzip = 1; } if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); if (!xen_state.loaded) { return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first")); } if ((xen_state.next_start.mod_start || xen_state.next_start.mod_len) && !xen_state.module_info_page) { return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("initrd already loaded")); } /* Leave one space for terminator. */ if (xen_state.n_modules >= MAX_MODULES - 1) { return grub_error (GRUB_ERR_BAD_ARGUMENT, "too many modules"); } if (!xen_state.module_info_page) { xen_state.n_modules = 0; xen_state.max_addr = ALIGN_UP (xen_state.max_addr, PAGE_SIZE); xen_state.modules_target_start = xen_state.max_addr; xen_state.next_start.mod_start = xen_state.max_addr + xen_state.xen_inf.virt_base; xen_state.next_start.flags |= SIF_MULTIBOOT_MOD; err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, xen_state.max_addr, MAX_MODULES * sizeof (xen_state.module_info_page [0])); if (err) return err; xen_state.module_info_page = get_virtual_current_address (ch); grub_memset (xen_state.module_info_page, 0, MAX_MODULES * sizeof (xen_state.module_info_page[0])); xen_state.max_addr += MAX_MODULES * sizeof (xen_state.module_info_page[0]); } xen_state.max_addr = ALIGN_UP (xen_state.max_addr, PAGE_SIZE); if (nounzip) grub_file_filter_disable_compression (); file = grub_file_open (argv[0]); if (!file) return grub_errno; size = grub_file_size (file); cmdline_len = grub_loader_cmdline_size (argc - 1, argv + 1); err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, xen_state.max_addr, cmdline_len); if (err) goto fail; grub_create_loader_cmdline (argc - 1, argv + 1, get_virtual_current_address (ch), cmdline_len); xen_state.module_info_page[xen_state.n_modules].cmdline = xen_state.max_addr - xen_state.modules_target_start; xen_state.max_addr = ALIGN_UP (xen_state.max_addr + cmdline_len, PAGE_SIZE); if (size) { err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, xen_state.max_addr, size); if (err) goto fail; if (grub_file_read (file, get_virtual_current_address (ch), size) != (grub_ssize_t) size) { if (!grub_errno) grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"), argv[0]); goto fail; } } xen_state.next_start.mod_len = xen_state.max_addr + size - xen_state.modules_target_start; xen_state.module_info_page[xen_state.n_modules].mod_start = xen_state.max_addr - xen_state.modules_target_start; xen_state.module_info_page[xen_state.n_modules].mod_end = xen_state.max_addr + size - xen_state.modules_target_start; xen_state.n_modules++; grub_dprintf ("xen", "module, addr=0x%x, size=0x%x\n", (unsigned) xen_state.max_addr, (unsigned) size); xen_state.max_addr = ALIGN_UP (xen_state.max_addr + size, PAGE_SIZE); fail: grub_file_close (file); return grub_errno; } static grub_command_t cmd_xen, cmd_initrd, cmd_module, cmd_multiboot; GRUB_MOD_INIT (xen) { cmd_xen = grub_register_command ("linux", grub_cmd_xen, 0, N_("Load Linux.")); cmd_multiboot = grub_register_command ("multiboot", grub_cmd_xen, 0, N_("Load Linux.")); cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, 0, N_("Load initrd.")); cmd_module = grub_register_command ("module", grub_cmd_module, 0, N_("Load module.")); my_mod = mod; } GRUB_MOD_FINI (xen) { grub_unregister_command (cmd_xen); grub_unregister_command (cmd_initrd); grub_unregister_command (cmd_multiboot); grub_unregister_command (cmd_module); }