grub2-dev: Porting

1 
1 5 Porting
1 *********
1 
1 GRUB2 is designed to be easily portable accross platforms.  But because
1 of the nature of bootloader every new port must be done separately.
1 Here is how I did MIPS (loongson and ARC) and Xen ports.  Note than this
1 is more of suggestions, not absolute truth.
1 
1    First of all grab any architecture specifications you can find in
1 public (please avoid NDA).
1 
1    First stage is "Hello world".  I've done it outside of GRUB for
1 simplicity.  Your task is to have a small program which is loadable as
1 bootloader and clearly shows its presence to you.  If you have easily
1 accessible console you can just print a message.  If you have a mapped
1 framebuffer you know address of, you can draw a square.  If you have a
1 debug facility, just hanging without crashing might be enough.  For the
1 first stage you can choose to load the bootloader across the network
1 since format for network image is often easier than for local boot and
1 it skips the need of small intermediary stages and nvram handling.
1 Additionally you can often have a good idea of the needed format by
1 running "file" on any netbootable executable for given platform.
1 
1    This program should probably have 2 parts: an assembler and C one.
1 Assembler one handles BSS cleaning and other needed setup (on some
1 platforms you may need to switch modes or copy the executable to its
1 definitive position).  So your code may look like (x86 assembly for
1 illustration purposes)
1 
1              .globl _start
1      _start:
1      	movl	$_bss_start, %edi
1      	movl	$_end, %ecx
1      	subl	%edi, %ecx
1      	xorl	%eax, %eax
1      	cld
1      	rep
1      	stosb
1              call main
1 
1 
1      static const char msg[] = "Hello, world";
1 
1      void
1      putchar (int c)
1      {
1        ...
1      }
1 
1      void
1      main (void)
1      {
1        const char *ptr = msg;
1        while (*ptr)
1          putchar (*ptr++);
1        while (1);
1      }
1 
1    Sometimes you need a third file: assembly stubs for
1 ABI-compatibility.
1 
1    Once this file is functional it's time to move it into GRUB2.  The
1 startup assembly file goes to grub-core/kern/$cpu/$platform/startup.S.
1 You should also include grub/symbol.h and replace call to entry point
1 with call to EXT_C(grub_main).  The C file goes to
1 grub-core/kern/$cpu/$platform/init.c and its entry point is renamed to
1 void grub_machine_init (void).  Keep final infinite loop for now.  Stubs
1 file if any goes to grub-core/kern/$cpu/$platform/callwrap.S. Sometimes
1 either $cpu or $platform is dropped if file is used on several cpus
1 respectivelyplatforms.  Check those locations if they already have what
1 you're looking for.
1 
1    Then modify in configure.ac the following parts:
1 
1    CPU names:
1 
1      case "$target_cpu" in
1        i[[3456]]86)	target_cpu=i386 ;;
1        amd64)	target_cpu=x86_64 ;;
1        sparc)	target_cpu=sparc64 ;;
1        s390x)	target_cpu=s390 ;;
1        ...
1      esac
1 
1    Sometimes CPU have additional architecture names which don't
1 influence booting.  You might want to have some canonical name to avoid
1 having bunch of identical platforms with different names.
1 
1    NOTE: it doesn't influence compile optimisations which depend solely
1 on chosen compiler and compile options.
1 
1      if test "x$with_platform" = x; then
1        case "$target_cpu"-"$target_vendor" in
1          i386-apple) platform=efi ;;
1          i386-*) platform=pc ;;
1          x86_64-apple) platform=efi ;;
1          x86_64-*) platform=pc ;;
1          powerpc-*) platform=ieee1275 ;;
1          ...
1        esac
1      else
1        ...
1      fi
1 
1    This part deals with guessing the platform from CPU and vendor.
1 Sometimes you need to use 32-bit mode for booting even if OS runs in
1 64-bit one.  If so add your platform to:
1 
1      case "$target_cpu"-"$platform" in
1        x86_64-efi) ;;
1        x86_64-emu) ;;
1        x86_64-*) target_cpu=i386 ;;
1        powerpc64-ieee1275) target_cpu=powerpc ;;
1      esac
1 
1    Add your platform to the list of supported ones:
1 
1      case "$target_cpu"-"$platform" in
1        i386-efi) ;;
1        x86_64-efi) ;;
1        i386-pc) ;;
1        i386-multiboot) ;;
1        i386-coreboot) ;;
1        ...
1      esac
1 
1    If explicit -m32 or -m64 is needed add it to:
1 
1      case "$target_cpu" in
1        i386 | powerpc) target_m32=1 ;;
1        x86_64 | sparc64) target_m64=1 ;;
1      esac
1 
1    Finally you need to add a conditional to the following block:
1 
1      AM_CONDITIONAL([COND_mips_arc], [test x$target_cpu = xmips -a x$platform = xarc])
1      AM_CONDITIONAL([COND_sparc64_ieee1275], [test x$target_cpu = xsparc64 -a x$platform = xieee1275])
1      AM_CONDITIONAL([COND_powerpc_ieee1275], [test x$target_cpu = xpowerpc -a x$platform = xieee1275])
1 
1    Next stop is gentpl.py.  You need to add your platform to the list of
1 supported ones (sorry that this list is duplicated):
1 
1      GRUB_PLATFORMS = [ "emu", "i386_pc", "i386_efi", "i386_qemu", "i386_coreboot",
1                         "i386_multiboot", "i386_ieee1275", "x86_64_efi",
1                         "mips_loongson", "sparc64_ieee1275",
1                         "powerpc_ieee1275", "mips_arc", "ia64_efi",
1                         "mips_qemu_mips", "s390_mainframe" ]
1 
1    You may also want already to add new platform to one or several of
1 available groups.  In particular we always have a group for each CPU
1 even when only one platform for given CPU is available.
1 
1    Then comes grub-core/Makefile.core.def.  In the block "kernel" you'll
1 need to define ldflags for your platform ($cpu_$platform_ldflags).  You
1 also need to declare startup asm file ($cpu_$platform_startup) as well
1 as any other files (e.g.  init.c and callwrap.S) (e.g.  $cpu_$platform =
1 kern/$cpu/$platform/init.c).  At this stage you will also need to add
1 dummy dl.c and cache.S with functions grub_err_t
1 grub_arch_dl_check_header (void *ehdr), grub_err_t
1 grub_arch_dl_relocate_symbols (grub_dl_t mod, void *ehdr) (dl.c) and
1 void grub_arch_sync_caches (void *address, grub_size_t len) (cache.S).
1 They won't be used for now.
1 
1    You will need to create directory include/$cpu/$platform and a file
1 include/$cpu/types.h.  The later folowing this template:
1 
1      #ifndef GRUB_TYPES_CPU_HEADER
1      #define GRUB_TYPES_CPU_HEADER	1
1 
1      /* The size of void *.  */
1      #define GRUB_TARGET_SIZEOF_VOID_P	4
1 
1      /* The size of long.  */
1      #define GRUB_TARGET_SIZEOF_LONG		4
1 
1      /* mycpu is big-endian.  */
1      #define GRUB_TARGET_WORDS_BIGENDIAN	1
1      /* Alternatively: mycpu is little-endian.  */
1      #undef GRUB_TARGET_WORDS_BIGENDIAN
1 
1      #endif /* ! GRUB_TYPES_CPU_HEADER */
1 
1    You will also need to add a dummy file to datetime and setjmp modules
1 to avoid any of it having no files.  It can be just completely empty at
1 this stage.
1 
1    You'll need to make grub-mkimage.c (util/grub_mkimage.c) aware of the
1 needed format.  For most commonly used formats like ELF, PE, aout or raw
1 the support is already present and you'll need to make it follow the
1 existant code paths for your platform adding adjustments if necessary.
1 When done compile:
1 
1      ./autogen.sh
1      ./configure --target=$cpu --with-platform=$platform TARGET_CC=.. OBJCOPY=... STRIP=...
1      make > /dev/null
1 
1    And create image
1 
1      ./grub-mkimage -d grub-core -O $format_id -o test.img
1 
1    And it's time to test your test.img.
1 
1    If it works next stage is to have heap, console and timer.
1 
1    To have the heap working you need to determine which regions are
1 suitable for heap usage, allocate them from firmware and map (if
1 applicable).  Then call grub_mm_init_region (vois *start, grub_size_t s)
1 for every of this region.  As a shortcut for early port you can allocate
1 right after _end or have a big static array for heap.  If you do you'll
1 probably need to come back to this later.  As for output console you
1 should distinguish between an array of text, terminfo or graphics-based
1 console.  Many of real-world examples don't fit perfectly into any of
1 these categories but one of the models is easier to be used as base.  In
1 second and third case you should add your platform to terminfokernel
1 respectively videoinkernel group.  A good example of array of text is
1 i386-pc (kern/i386/pc/init.c and term/i386/pc/console.c).  Of terminfo
1 is ieee1275 (kern/ieee1275/init.c and term/ieee1275/console.c).  Of
1 video is loongson (kern/mips/loongson/init.c).  Note that terminfo has
1 to be inited in 2 stages: one before (to get at least rudimentary
1 console as early as possible) and another after the heap (to get
1 full-featured console).  For the input there are string of keys,
1 terminfo and direct hardware.  For string of keys look at i386-pc (same
1 files), for termino ieee1275 (same files) and for hardware loongson
1 (kern/mips/loongson/init.c and term/at_keyboard.c).
1 
1    For the timer you'll need to call grub_install_get_time_ms (...)
1 with as sole argument a function returning a grub_uint64_t of a number
1 of milliseconds elapsed since arbitrary point in the past.
1 
1    Once these steps accomplished you can remove the inifinite loop and
1 you should be able to get to the minimal console.  Next step is to have
1 module loading working.  For this you'll need to fill kern/$cpu/dl.c and
1 kern/$cpu/cache.S with real handling of relocations and respectively the
1 real sync of I and D caches.  Also you'll need to decide where in the
1 image to store the modules.  Usual way is to have it concatenated at the
1 end.  In this case you'll need to modify startup.S to copy modules out
1 of bss to let's say ALIGN_UP (_end, 8) before cleaning out bss.  You'll
1 probably find useful to add total_module_size field to startup.S. In
1 init.c you need to set grub_modbase to the address where modules can be
1 found.  You may need grub_modules_get_end () to avoid declaring the
1 space occupied by modules as usable for heap.  You can test modules
1 with:
1 
1      ./grub-mkimage -d grub-core -O $format_id -o test.img hello
1 
1    and then running "hello" in the shell.
1 
1    Once this works, you should think of implementing disk access.  Look
1 around disk/ for examples.
1 
1    Then, very importantly, you probably need to implement the actual
1 loader (examples available in loader/)
1 
1    Last step to have minimally usable port is to add support to
1 grub-install to put GRUB in a place where firmware or platform will pick
1 it up.
1 
1    Next steps are: filling datetime.c, setjmp.S, network (net/drivers),
1 video (video/), halt (lib/), reboot (lib/).
1 
1    Please add your platform to Platform limitations and Supported
1 kernels chapter in user documentation and mention any steps you skipped
1 which result in reduced features or performance.  Here is the quick
1 checklist of features.  Some of them are less important than others and
1 skipping them is completely ok, just needs to be mentioned in user
1 documentation.
1 
1    Checklist:
1    * Is heap big enough?
1    * Which charset is supported by console?
1    * Does platform have disk driver?
1    * Do you have network card support?
1    * Are you able to retrieve datetime (with date)?
1    * Are you able to set datetime (with date)?
1    * Is serial supported?
1    * Do you have direct disk support?
1    * Do you have direct keyboard support?
1    * Do you have USB support?
1    * Do you support loading through network?
1    * Do you support loading from disk?
1    * Do you support chainloading?
1    * Do you support network chainloading?
1    * Does cpuid command supports checking all CPU features that the user
1      might want conditionalise on (64-bit mode, hypervisor,...)
1    * Do you support hints?  How reliable are they?
1    * Does platform have ACPI? If so do "acpi" and "lsacpi" modules work?
1    * Do any of platform-specific operations mentioned in the relevant
1      section of user manual makes sense on your platform?
1    * Does your platform support PCI? If so is there an appropriate
1      driver for GRUB?
1    * Do you support badram?
1