static struct gunzip_state gzstate;
 
 struct addr_range {
-       unsigned long addr;
+       void *addr;
        unsigned long size;
-       unsigned long memsize;
 };
-static struct addr_range vmlinux;
-static struct addr_range vmlinuz;
-static struct addr_range initrd;
-
-static unsigned long elfoffset;
-static int is_64bit;
 
-static char elfheader[256];
+struct elf_info {
+       unsigned long loadsize;
+       unsigned long memsize;
+       unsigned long elfoffset;
+};
 
 typedef void (*kernel_entry_t)(unsigned long, unsigned long, void *);
 
 #undef DEBUG
 
-static int is_elf64(void *hdr)
+static int parse_elf64(void *hdr, struct elf_info *info)
 {
        Elf64_Ehdr *elf64 = hdr;
        Elf64_Phdr *elf64ph;
        if (i >= (unsigned int)elf64->e_phnum)
                return 0;
 
-       elfoffset = (unsigned long)elf64ph->p_offset;
-       vmlinux.size = (unsigned long)elf64ph->p_filesz;
-       vmlinux.memsize = (unsigned long)elf64ph->p_memsz;
+       info->loadsize = (unsigned long)elf64ph->p_filesz;
+       info->memsize = (unsigned long)elf64ph->p_memsz;
+       info->elfoffset = (unsigned long)elf64ph->p_offset;
 
-       is_64bit = 1;
        return 1;
 }
 
-static int is_elf32(void *hdr)
+static int parse_elf32(void *hdr, struct elf_info *info)
 {
        Elf32_Ehdr *elf32 = hdr;
        Elf32_Phdr *elf32ph;
              elf32->e_machine         == EM_PPC))
                return 0;
 
-       elf32 = (Elf32_Ehdr *)elfheader;
        elf32ph = (Elf32_Phdr *) ((unsigned long)elf32 + elf32->e_phoff);
        for (i = 0; i < elf32->e_phnum; i++, elf32ph++)
                if (elf32ph->p_type == PT_LOAD)
        if (i >= elf32->e_phnum)
                return 0;
 
-       elfoffset = elf32ph->p_offset;
-       vmlinux.size = elf32ph->p_filesz;
-       vmlinux.memsize = elf32ph->p_memsz;
+       info->loadsize = elf32ph->p_filesz;
+       info->memsize = elf32ph->p_memsz;
+       info->elfoffset = elf32ph->p_offset;
        return 1;
 }
 
-static void prep_kernel(unsigned long a1, unsigned long a2)
+static struct addr_range prep_kernel(void)
 {
+       char elfheader[256];
+       void *vmlinuz_addr = _vmlinux_start;
+       unsigned long vmlinuz_size = _vmlinux_end - _vmlinux_start;
+       void *addr = 0;
+       struct elf_info ei;
        int len;
 
-       vmlinuz.addr = (unsigned long)_vmlinux_start;
-       vmlinuz.size = (unsigned long)(_vmlinux_end - _vmlinux_start);
-
        /* gunzip the ELF header of the kernel */
-       gunzip_start(&gzstate, (void *)vmlinuz.addr, vmlinuz.size);
+       gunzip_start(&gzstate, vmlinuz_addr, vmlinuz_size);
        gunzip_exactly(&gzstate, elfheader, sizeof(elfheader));
 
-       if (!is_elf64(elfheader) && !is_elf32(elfheader)) {
+       if (!parse_elf64(elfheader, &ei) && !parse_elf32(elfheader, &ei)) {
                printf("Error: not a valid PPC32 or PPC64 ELF file!\n\r");
                exit();
        }
         * the kernel bss must be claimed (it will be zero'd by the
         * kernel itself)
         */
-       printf("Allocating 0x%lx bytes for kernel ...\n\r", vmlinux.memsize);
-       vmlinux.addr = (unsigned long)malloc(vmlinux.memsize);
-       if (vmlinux.addr == 0) {
-               printf("Can't allocate memory for kernel image !\n\r");
-               exit();
+       printf("Allocating 0x%lx bytes for kernel ...\n\r", ei.memsize);
+
+       if (platform_ops.vmlinux_alloc) {
+               addr = platform_ops.vmlinux_alloc(ei.memsize);
+       } else {
+               if ((unsigned long)_start < ei.memsize) {
+                       printf("Insufficient memory for kernel at address 0!"
+                              " (_start=%lx)\n\r", _start);
+                       exit();
+               }
        }
 
+       /* Finally, gunzip the kernel */
+       printf("gunzipping (0x%p <- 0x%p:0x%p)...", addr,
+              vmlinuz_addr, vmlinuz_addr+vmlinuz_size);
+       /* discard up to the actual load data */
+       gunzip_discard(&gzstate, ei.elfoffset - sizeof(elfheader));
+       len = gunzip_finish(&gzstate, addr, ei.memsize);
+       printf("done 0x%lx bytes\n\r", len);
+
+       flush_cache(addr, ei.loadsize);
+
+       return (struct addr_range){addr, ei.memsize};
+}
+
+static struct addr_range prep_initrd(struct addr_range vmlinux,
+                                    unsigned long initrd_addr,
+                                    unsigned long initrd_size)
+{
+       void *devp;
+       u32 initrd_start, initrd_end;
+
+       /* If we have an image attached to us, it overrides anything
+        * supplied by the loader. */
+       if (_initrd_end > _initrd_start) {
+               printf("Attached initrd image at 0x%p-0x%p\n\r",
+                      _initrd_start, _initrd_end);
+               initrd_addr = (unsigned long)_initrd_start;
+               initrd_size = _initrd_end - _initrd_start;
+       } else if (initrd_size > 0) {
+               printf("Using loader supplied ramdisk at 0x%lx-0x%lx\n\r",
+                      initrd_addr, initrd_addr + initrd_size);
+       }
+
+       /* If there's no initrd at all, we're done */
+       if (! initrd_size)
+               return (struct addr_range){0, 0};
+
        /*
-        * Now find the initrd
-        *
-        * First see if we have an image attached to us.  If so
-        * allocate memory for it and copy it there.
+        * If the initrd is too low it will be clobbered when the
+        * kernel relocates to its final location.  In this case,
+        * allocate a safer place and move it.
         */
-       initrd.size = (unsigned long)(_initrd_end - _initrd_start);
-       initrd.memsize = initrd.size;
-       if (initrd.size > 0) {
+       if (initrd_addr < vmlinux.size) {
+               void *old_addr = (void *)initrd_addr;
+
                printf("Allocating 0x%lx bytes for initrd ...\n\r",
-                      initrd.size);
-               initrd.addr = (unsigned long)malloc((u32)initrd.size);
-               if (initrd.addr == 0) {
+                      initrd_size);
+               initrd_addr = (unsigned long)malloc(initrd_size);
+               if (! initrd_addr) {
                        printf("Can't allocate memory for initial "
-                                       "ramdisk !\n\r");
+                              "ramdisk !\n\r");
                        exit();
                }
-               printf("initial ramdisk moving 0x%lx <- 0x%lx "
-                       "(0x%lx bytes)\n\r", initrd.addr,
-                       (unsigned long)_initrd_start, initrd.size);
-               memmove((void *)initrd.addr, (void *)_initrd_start,
-                       initrd.size);
-               printf("initrd head: 0x%lx\n\r",
-                               *((unsigned long *)initrd.addr));
-       } else if (a2 != 0) {
-               /* Otherwise, see if yaboot or another loader gave us an initrd */
-               initrd.addr = a1;
-               initrd.memsize = initrd.size = a2;
-               printf("Using loader supplied initrd at 0x%lx (0x%lx bytes)\n\r",
-                      initrd.addr, initrd.size);
+               printf("Relocating initrd 0x%p <- 0x%p (0x%lx bytes)\n\r",
+                      initrd_addr, old_addr, initrd_size);
+               memmove((void *)initrd_addr, old_addr, initrd_size);
        }
 
-       /* Eventually gunzip the kernel */
-       printf("gunzipping (0x%lx <- 0x%lx:0x%0lx)...",
-              vmlinux.addr, vmlinuz.addr, vmlinuz.addr+vmlinuz.size);
-       /* discard up to the actual load data */
-       gunzip_discard(&gzstate, elfoffset - sizeof(elfheader));
-       len = gunzip_finish(&gzstate, (void *)vmlinux.addr,
-                           vmlinux.memsize);
-       printf("done 0x%lx bytes\n\r", len);
+       printf("initrd head: 0x%lx\n\r", *((unsigned long *)initrd_addr));
+
+       /* Tell the kernel initrd address via device tree */
+       devp = finddevice("/chosen");
+       if (! devp) {
+               printf("Device tree has no chosen node!\n\r");
+               exit();
+       }
+
+       initrd_start = (u32)initrd_addr;
+       initrd_end = (u32)initrd_addr + initrd_size;
+
+       setprop(devp, "linux,initrd-start", &initrd_start,
+               sizeof(initrd_start));
+       setprop(devp, "linux,initrd-end", &initrd_end, sizeof(initrd_end));
 
-       flush_cache((void *)vmlinux.addr, vmlinux.size);
+       return (struct addr_range){(void *)initrd_addr, initrd_size};
 }
 
 /* A buffer that may be edited by tools operating on a zImage binary so as to
 
 void start(unsigned long a1, unsigned long a2, void *promptr, void *sp)
 {
+       struct addr_range vmlinux, initrd;
        kernel_entry_t kentry;
        char cmdline[COMMAND_LINE_SIZE];
        unsigned long ft_addr = 0;
        printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r",
               _start, sp);
 
-       prep_kernel(a1, a2);
+       vmlinux = prep_kernel();
+       initrd = prep_initrd(vmlinux, a1, a2);
 
        /* If cmdline came from zimage wrapper or if we can edit the one
         * in the dt, print it out and edit it, if possible.
        if (ft_addr)
                kentry(ft_addr, 0, NULL);
        else
-               /* XXX initrd addr/size should be passed in properties */
-               kentry(initrd.addr, initrd.size, promptr);
+               kentry((unsigned long)initrd.addr, initrd.size, promptr);
 
        /* console closed so printf below may not work */
        printf("Error: Linux kernel returned to zImage boot wrapper!\n\r");