0a40e16be3393d598cfe913eb25fbc746709d213
[grub.git] / grub-core / loader / arm64 / xen_boot.c
1 /*
2  *  GRUB  --  GRand Unified Bootloader
3  *  Copyright (C) 2014  Free Software Foundation, Inc.
4  *
5  *  GRUB is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  GRUB is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <grub/cache.h>
20 #include <grub/charset.h>
21 #include <grub/command.h>
22 #include <grub/err.h>
23 #include <grub/file.h>
24 #include <grub/fdt.h>
25 #include <grub/list.h>
26 #include <grub/loader.h>
27 #include <grub/misc.h>
28 #include <grub/mm.h>
29 #include <grub/types.h>
30 #include <grub/cpu/linux.h>
31 #include <grub/efi/efi.h>
32 #include <grub/efi/fdtload.h>
33 #include <grub/efi/memory.h>
34 #include <grub/efi/pe32.h>      /* required by struct xen_hypervisor_header */
35 #include <grub/i18n.h>
36 #include <grub/lib/cmdline.h>
37
38 GRUB_MOD_LICENSE ("GPLv3+");
39
40 #define XEN_HYPERVISOR_NAME  "xen_hypervisor"
41 #define MODULE_CUSTOM_COMPATIBLE  "multiboot,module"
42
43 /* This maximum size is defined in Power.org ePAPR V1.1
44  * https://www.power.org/documentation/epapr-version-1-1/
45  * 2.2.1.1 Node Name Requirements
46  * node-name@unit-address
47  * 31 + 1(@) + 16(64bit address in hex format) + 1(\0) = 49
48  */
49 #define FDT_NODE_NAME_MAX_SIZE  (49)
50
51 struct compat_string_struct
52 {
53   grub_size_t size;
54   const char *compat_string;
55 };
56 typedef struct compat_string_struct compat_string_struct_t;
57 #define FDT_COMPATIBLE(x) {.size = sizeof(x), .compat_string = (x)}
58
59 enum module_type
60 {
61   MODULE_IMAGE,
62   MODULE_INITRD,
63   MODULE_XSM,
64   MODULE_CUSTOM
65 };
66 typedef enum module_type module_type_t;
67
68 struct xen_hypervisor_header
69 {
70   struct grub_arm64_linux_kernel_header efi_head;
71
72   /* This is always PE\0\0.  */
73   grub_uint8_t signature[GRUB_PE32_SIGNATURE_SIZE];
74   /* The COFF file header.  */
75   struct grub_pe32_coff_header coff_header;
76   /* The Optional header.  */
77   struct grub_pe64_optional_header optional_header;
78 };
79
80 struct xen_boot_binary
81 {
82   struct xen_boot_binary *next;
83   struct xen_boot_binary **prev;
84   int is_hypervisor;
85
86   grub_addr_t start;
87   grub_size_t size;
88   grub_size_t align;
89
90   char *cmdline;
91   int cmdline_size;
92 };
93
94 static grub_dl_t my_mod;
95
96 static int loaded;
97
98 static struct xen_boot_binary *xen_hypervisor;
99 static struct xen_boot_binary *module_head;
100
101 static __inline grub_addr_t
102 xen_boot_address_align (grub_addr_t start, grub_size_t align)
103 {
104   return (align ? (ALIGN_UP (start, align)) : start);
105 }
106
107 static grub_err_t
108 prepare_xen_hypervisor_params (void *xen_boot_fdt)
109 {
110   int chosen_node = 0;
111   int retval;
112
113   chosen_node = grub_fdt_find_subnode (xen_boot_fdt, 0, "chosen");
114   if (chosen_node < 0)
115     chosen_node = grub_fdt_add_subnode (xen_boot_fdt, 0, "chosen");
116   if (chosen_node < 1)
117     return grub_error (GRUB_ERR_IO, "failed to get chosen node in FDT");
118
119   /*
120    * The address and size are always written using 64-bits value. Set
121    * #address-cells and #size-cells accordingly.
122    */
123   retval = grub_fdt_set_prop32 (xen_boot_fdt, chosen_node, "#address-cells", 2);
124   if (retval)
125     return grub_error (GRUB_ERR_IO, "failed to set #address-cells");
126   retval = grub_fdt_set_prop32 (xen_boot_fdt, chosen_node, "#size-cells", 2);
127   if (retval)
128     return grub_error (GRUB_ERR_IO, "failed to set #size-cells");
129
130   grub_dprintf ("xen_loader",
131                 "Xen Hypervisor cmdline : %s @ %p size:%d\n",
132                 xen_hypervisor->cmdline, xen_hypervisor->cmdline,
133                 xen_hypervisor->cmdline_size);
134
135   retval = grub_fdt_set_prop (xen_boot_fdt, chosen_node, "bootargs",
136                               xen_hypervisor->cmdline,
137                               xen_hypervisor->cmdline_size);
138   if (retval)
139     return grub_error (GRUB_ERR_IO, "failed to install/update FDT");
140
141   return GRUB_ERR_NONE;
142 }
143
144 static grub_err_t
145 prepare_xen_module_params (struct xen_boot_binary *module, void *xen_boot_fdt)
146 {
147   int retval, chosen_node = 0, module_node = 0;
148   char module_name[FDT_NODE_NAME_MAX_SIZE];
149
150   retval = grub_snprintf (module_name, FDT_NODE_NAME_MAX_SIZE, "module@%lx",
151                           xen_boot_address_align (module->start,
152                                                   module->align));
153   grub_dprintf ("xen_loader", "Module node name %s \n", module_name);
154
155   if (retval < (int) sizeof ("module@"))
156     return grub_error (GRUB_ERR_IO, N_("failed to get FDT"));
157
158   chosen_node = grub_fdt_find_subnode (xen_boot_fdt, 0, "chosen");
159   if (chosen_node < 0)
160     chosen_node = grub_fdt_add_subnode (xen_boot_fdt, 0, "chosen");
161   if (chosen_node < 1)
162     return grub_error (GRUB_ERR_IO, "failed to get chosen node in FDT");
163
164   module_node =
165     grub_fdt_find_subnode (xen_boot_fdt, chosen_node, module_name);
166   if (module_node < 0)
167     module_node =
168       grub_fdt_add_subnode (xen_boot_fdt, chosen_node, module_name);
169
170   retval = grub_fdt_set_prop (xen_boot_fdt, module_node, "compatible",
171                               MODULE_CUSTOM_COMPATIBLE, sizeof(MODULE_CUSTOM_COMPATIBLE));
172   if (retval)
173     return grub_error (GRUB_ERR_IO, "failed to update FDT");
174
175   grub_dprintf ("xen_loader", "Module\n");
176
177   retval = grub_fdt_set_reg64 (xen_boot_fdt, module_node,
178                                xen_boot_address_align (module->start,
179                                                        module->align),
180                                module->size);
181   if (retval)
182     return grub_error (GRUB_ERR_IO, "failed to update FDT");
183
184   if (module->cmdline && module->cmdline_size > 0)
185     {
186       grub_dprintf ("xen_loader",
187                     "Module cmdline : %s @ %p size:%d\n",
188                     module->cmdline, module->cmdline, module->cmdline_size);
189
190       retval = grub_fdt_set_prop (xen_boot_fdt, module_node, "bootargs",
191                                   module->cmdline, module->cmdline_size + 1);
192       if (retval)
193         return grub_error (GRUB_ERR_IO, "failed to update FDT");
194     }
195   else
196     {
197       grub_dprintf ("xen_loader", "Module has no bootargs!\n");
198     }
199
200   return GRUB_ERR_NONE;
201 }
202
203 static grub_err_t
204 finalize_params_xen_boot (void)
205 {
206   struct xen_boot_binary *module;
207   void *xen_boot_fdt;
208   grub_size_t additional_size = 0x1000;
209
210   /* Hypervisor.  */
211   additional_size += FDT_NODE_NAME_MAX_SIZE + xen_hypervisor->cmdline_size;
212   FOR_LIST_ELEMENTS (module, module_head)
213   {
214     additional_size += 6 * FDT_NODE_NAME_MAX_SIZE + sizeof(MODULE_CUSTOM_COMPATIBLE) - 1
215       + module->cmdline_size;
216   }
217
218   xen_boot_fdt = grub_fdt_load (additional_size);
219   if (!xen_boot_fdt)
220     return grub_error (GRUB_ERR_IO, "failed to get FDT");
221
222   if (xen_hypervisor)
223     {
224       if (prepare_xen_hypervisor_params (xen_boot_fdt) != GRUB_ERR_NONE)
225         goto fail;
226     }
227   else
228     {
229       grub_dprintf ("xen_loader", "Failed to get Xen Hypervisor info!\n");
230       goto fail;
231     }
232
233   /* Set module params info */
234   FOR_LIST_ELEMENTS (module, module_head)
235   {
236     if (module->start && module->size > 0)
237       {
238         grub_dprintf ("xen_loader", "Module @ 0x%lx size:0x%lx\n",
239                       xen_boot_address_align (module->start, module->align),
240                       module->size);
241         if (prepare_xen_module_params (module, xen_boot_fdt) != GRUB_ERR_NONE)
242           goto fail;
243       }
244     else
245       {
246         grub_dprintf ("xen_loader", "Module info error!\n");
247         goto fail;
248       }
249   }
250
251   if (grub_fdt_install() == GRUB_ERR_NONE)
252     return GRUB_ERR_NONE;
253
254 fail:
255   grub_fdt_unload ();
256
257   return grub_error (GRUB_ERR_IO, "failed to install/update FDT");
258 }
259
260
261 static grub_err_t
262 xen_boot (void)
263 {
264   grub_err_t err = finalize_params_xen_boot ();
265   if (err)
266     return err;
267
268   return grub_arm64_uefi_boot_image (xen_hypervisor->start,
269                                      xen_hypervisor->size,
270                                      xen_hypervisor->cmdline);
271 }
272
273 static void
274 single_binary_unload (struct xen_boot_binary *binary)
275 {
276   if (!binary)
277     return;
278
279   if (binary->start && binary->size > 0)
280     {
281       grub_efi_free_pages ((grub_efi_physical_address_t) binary->start,
282                            GRUB_EFI_BYTES_TO_PAGES (binary->size + binary->align));
283     }
284
285   if (binary->cmdline && binary->cmdline_size > 0)
286     {
287       grub_free (binary->cmdline);
288       grub_dprintf ("xen_loader",
289                     "Module cmdline memory free @ %p size: %d\n",
290                     binary->cmdline, binary->cmdline_size);
291     }
292
293   if (!binary->is_hypervisor)
294     grub_list_remove (GRUB_AS_LIST (binary));
295
296   grub_dprintf ("xen_loader",
297                 "Module struct memory free @ %p size: 0x%lx\n",
298                 binary, sizeof (binary));
299   grub_free (binary);
300
301   return;
302 }
303
304 static void
305 all_binaries_unload (void)
306 {
307   struct xen_boot_binary *binary;
308
309   FOR_LIST_ELEMENTS (binary, module_head)
310   {
311     single_binary_unload (binary);
312   }
313
314   if (xen_hypervisor)
315     single_binary_unload (xen_hypervisor);
316
317   return;
318 }
319
320 static grub_err_t
321 xen_unload (void)
322 {
323   loaded = 0;
324   all_binaries_unload ();
325   grub_fdt_unload ();
326   grub_dl_unref (my_mod);
327
328   return GRUB_ERR_NONE;
329 }
330
331 static void
332 xen_boot_binary_load (struct xen_boot_binary *binary, grub_file_t file,
333                       int argc, char *argv[])
334 {
335   binary->size = grub_file_size (file);
336   grub_dprintf ("xen_loader", "Xen_boot file size: 0x%lx\n", binary->size);
337
338   binary->start
339     = (grub_addr_t) grub_efi_allocate_any_pages (GRUB_EFI_BYTES_TO_PAGES
340                                                  (binary->size +
341                                                   binary->align));
342   if (!binary->start)
343     {
344       grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
345       return;
346     }
347
348   grub_dprintf ("xen_loader", "Xen_boot numpages: 0x%lx\n",
349                 GRUB_EFI_BYTES_TO_PAGES (binary->size + binary->align));
350
351   if (grub_file_read (file, (void *) xen_boot_address_align (binary->start,
352                                                              binary->align),
353                       binary->size) != (grub_ssize_t) binary->size)
354     {
355       single_binary_unload (binary);
356       grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), argv[0]);
357       return;
358     }
359
360   if (argc > 1)
361     {
362       binary->cmdline_size = grub_loader_cmdline_size (argc - 1, argv + 1);
363       binary->cmdline = grub_zalloc (binary->cmdline_size);
364       if (!binary->cmdline)
365         {
366           single_binary_unload (binary);
367           grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
368           return;
369         }
370       grub_create_loader_cmdline (argc - 1, argv + 1, binary->cmdline,
371                                   binary->cmdline_size);
372       grub_dprintf ("xen_loader",
373                     "Xen_boot cmdline @ %p %s, size: %d\n",
374                     binary->cmdline, binary->cmdline, binary->cmdline_size);
375     }
376   else
377     {
378       binary->cmdline_size = 0;
379       binary->cmdline = NULL;
380     }
381
382   grub_errno = GRUB_ERR_NONE;
383   return;
384 }
385
386 static grub_err_t
387 grub_cmd_xen_module (grub_command_t cmd __attribute__((unused)),
388                      int argc, char *argv[])
389 {
390
391   struct xen_boot_binary *module = NULL;
392   grub_file_t file = 0;
393   int nounzip = 0;
394
395   if (!argc)
396     {
397       grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
398       goto fail;
399     }
400
401   if (grub_strcmp (argv[0], "--nounzip") == 0)
402     {
403       argv++;
404       argc--;
405       nounzip = 1;
406     }
407
408   if (!argc)
409     {
410       grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
411       goto fail;
412     }
413
414   if (!loaded)
415     {
416       grub_error (GRUB_ERR_BAD_ARGUMENT,
417                   N_("you need to load the Xen Hypervisor first"));
418       goto fail;
419     }
420
421   module =
422     (struct xen_boot_binary *) grub_zalloc (sizeof (struct xen_boot_binary));
423   if (!module)
424     return grub_errno;
425
426   module->is_hypervisor = 0;
427   module->align = 4096;
428
429   grub_dprintf ("xen_loader", "Init module and node info\n");
430
431   if (nounzip)
432     grub_file_filter_disable_compression ();
433   file = grub_file_open (argv[0]);
434   if (!file)
435     goto fail;
436
437   xen_boot_binary_load (module, file, argc, argv);
438   if (grub_errno == GRUB_ERR_NONE)
439     grub_list_push (GRUB_AS_LIST_P (&module_head), GRUB_AS_LIST (module));
440
441  fail:
442   if (file)
443     grub_file_close (file);
444   if (grub_errno != GRUB_ERR_NONE)
445     single_binary_unload (module);
446
447   return grub_errno;
448 }
449
450 static grub_err_t
451 grub_cmd_xen_hypervisor (grub_command_t cmd __attribute__ ((unused)),
452                          int argc, char *argv[])
453 {
454   struct xen_hypervisor_header sh;
455   grub_file_t file = NULL;
456
457   grub_dl_ref (my_mod);
458
459   if (!argc)
460     {
461       grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
462       goto fail;
463     }
464
465   file = grub_file_open (argv[0]);
466   if (!file)
467     goto fail;
468
469   if (grub_file_read (file, &sh, sizeof (sh)) != (long) sizeof (sh))
470     goto fail;
471   if (grub_arm64_uefi_check_image
472       ((struct grub_arm64_linux_kernel_header *) &sh) != GRUB_ERR_NONE)
473     goto fail;
474   grub_file_seek (file, 0);
475
476   /* if another module has called grub_loader_set,
477      we need to make sure that another module is unloaded properly */
478   grub_loader_unset ();
479
480   xen_hypervisor =
481     (struct xen_boot_binary *) grub_zalloc (sizeof (struct xen_boot_binary));
482   if (!xen_hypervisor)
483     return grub_errno;
484
485   xen_hypervisor->is_hypervisor = 1;
486   xen_hypervisor->align = (grub_size_t) sh.optional_header.section_alignment;
487
488   xen_boot_binary_load (xen_hypervisor, file, argc, argv);
489   if (grub_errno == GRUB_ERR_NONE)
490     {
491       grub_loader_set (xen_boot, xen_unload, 0);
492       loaded = 1;
493     }
494
495 fail:
496   if (file)
497     grub_file_close (file);
498   if (grub_errno != GRUB_ERR_NONE)
499     {
500       loaded = 0;
501       all_binaries_unload ();
502       grub_dl_unref (my_mod);
503     }
504
505   return grub_errno;
506 }
507
508 static grub_command_t cmd_xen_hypervisor;
509 static grub_command_t cmd_xen_module;
510
511 GRUB_MOD_INIT (xen_boot)
512 {
513   cmd_xen_hypervisor =
514     grub_register_command ("xen_hypervisor", grub_cmd_xen_hypervisor, 0,
515                            N_("Load a xen hypervisor."));
516   cmd_xen_module =
517     grub_register_command ("xen_module", grub_cmd_xen_module, 0,
518                            N_("Load a xen module."));
519   my_mod = mod;
520 }
521
522 GRUB_MOD_FINI (xen_boot)
523 {
524   grub_unregister_command (cmd_xen_hypervisor);
525   grub_unregister_command (cmd_xen_module);
526 }