ia64/xen-unstable

changeset 16962:67ca9c37ef02

Direct Linux boot: Support booting non-relocatable Linux kernels.

This patch introduces a hack to make non-relocatable kernels
bootable too. Non-relocatable kernels absolutely want to run
at 0x100000 and are not at all happy about being at 0x200000.
Fortunately, thanks to crazy programs like LOADLIN, Linux has
a couple of hooks in its boot process which can be used to
play games. The 'code32_switch' hook is executed immediately
following the switch to protected mode.

So, this patch installs a hook at 0x200000+kernel_size. The hook
is hand crafted assembly which sets up all the segments as needed,
then essentially does memmove(0x100000,0x200000,kernel_size) and
finally does an unconditional jmp to 0x100000.

Amazingly this actually really does work. It has been successfully
tested with RHEL-2.1 and Fedora Core 6 install kernels on i386, and
Fedora Core 6 and 7 kernels on x86_64.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
author Keir Fraser <keir.fraser@citrix.com>
date Fri Feb 01 11:16:37 2008 +0000 (2008-02-01)
parents 17cce0554151
children 396ab902b02d
files tools/ioemu/hw/pc.c
line diff
     1.1 --- a/tools/ioemu/hw/pc.c	Fri Feb 01 11:14:53 2008 +0000
     1.2 +++ b/tools/ioemu/hw/pc.c	Fri Feb 01 11:16:37 2008 +0000
     1.3 @@ -417,6 +417,90 @@ static void generate_bootsect(uint32_t g
     1.4      bdrv_set_boot_sector(bs_table[0], bootsect, sizeof(bootsect));
     1.5  }
     1.6  
     1.7 +/*
     1.8 + * Evil helper for non-relocatable kernels
     1.9 + *
    1.10 + * So it works out like this:
    1.11 + *
    1.12 + *  0x100000  - Xen HVM firmware lives here. Kernel wants to boot here
    1.13 + *
    1.14 + * You can't both live there and HVM firmware is needed first, thus
    1.15 + * our plan is
    1.16 + *
    1.17 + *  0x200000              - kernel is loaded here by QEMU
    1.18 + *  0x200000+kernel_size  - helper code is put here by QEMU
    1.19 + *
    1.20 + * code32_switch in kernel header is set to point at out helper
    1.21 + * code at 0x200000+kernel_size
    1.22 + *
    1.23 + * Our helper basically does memmove(0x100000,0x200000,kernel_size)
    1.24 + * and then jmps to  0x1000000.
    1.25 + *
    1.26 + * So we've overwritten the HVM firmware (which was no longer
    1.27 + * needed) and the non-relocatable kernel can happily boot
    1.28 + * at its usual address.
    1.29 + *
    1.30 + * Simple, eh ?
    1.31 + *
    1.32 + * Well the assembler needed to do this is fairly short:
    1.33 + *
    1.34 + *  # Load segments
    1.35 + *    cld                         
    1.36 + *    cli                         
    1.37 + *    movl $0x18,%eax
    1.38 + *    mov %ax,%ds                 
    1.39 + *    mov %ax,%es                 
    1.40 + *    mov %ax,%fs                 
    1.41 + *    mov %ax,%gs                 
    1.42 + *    mov %ax,%ss                 
    1.43 + *
    1.44 + *  # Move the kernel into position
    1.45 + *    xor    %edx,%edx            
    1.46 + *_doloop:                        
    1.47 + *    movzbl 0x600000(%edx),%eax  
    1.48 + *    mov    %al,0x100000(%edx)   
    1.49 + *    add    $0x1,%edx            
    1.50 + *    cmp    $0x500000,%edx       
    1.51 + *    jne    _doloop              
    1.52 + *
    1.53 + *  # start kernel
    1.54 + *    xorl %ebx,%ebx              
    1.55 + *    mov    $0x100000,%ecx       
    1.56 + *    jmp    *%ecx                
    1.57 + *
    1.58 + */
    1.59 +static void setup_relocator(target_phys_addr_t addr, target_phys_addr_t src, target_phys_addr_t dst, size_t len)
    1.60 +{
    1.61 +  /* Now this assembler corresponds to follow machine code, with our args from QEMU spliced in :-) */
    1.62 +  unsigned char buf[] = {
    1.63 +    /* Load segments */
    1.64 +    0xfc,                         /* cld               */
    1.65 +    0xfa,                         /* cli               */ 
    1.66 +    0xb8, 0x18, 0x00, 0x00, 0x00, /* mov    $0x18,%eax */
    1.67 +    0x8e, 0xd8,                   /* mov    %eax,%ds   */
    1.68 +    0x8e, 0xc0,                   /* mov    %eax,%es   */
    1.69 +    0x8e, 0xe0,                   /* mov    %eax,%fs   */
    1.70 +    0x8e, 0xe8,                   /* mov    %eax,%gs   */
    1.71 +    0x8e, 0xd0,                   /* mov    %eax,%ss   */
    1.72 +    0x31, 0xd2,                   /* xor    %edx,%edx  */
    1.73 +  
    1.74 +    /* Move the kernel into position */
    1.75 +    0x0f, 0xb6, 0x82, (src&0xff), ((src>>8)&0xff), ((src>>16)&0xff), ((src>>24)&0xff), /*   movzbl $src(%edx),%eax */
    1.76 +    0x88, 0x82, (dst&0xff), ((dst>>8)&0xff), ((dst>>16)&0xff), ((dst>>24)&0xff),       /*   mov    %al,$dst(%edx)  */
    1.77 +    0x83, 0xc2, 0x01,                                                                  /*   add    $0x1,%edx       */
    1.78 +    0x81, 0xfa, (len&0xff), ((len>>8)&0xff), ((len>>16)&0xff), ((len>>24)&0xff),       /*   cmp    $len,%edx       */
    1.79 +    0x75, 0xe8,                                                                        /*   jne    13 <_doloop>    */
    1.80 +
    1.81 +    /* Start kernel */
    1.82 +    0x31, 0xdb,                                                                        /*   xor    %ebx,%ebx       */
    1.83 +    0xb9, (dst&0xff), ((dst>>8)&0xff), ((dst>>16)&0xff), ((dst>>24)&0xff),             /*   mov    $dst,%ecx  */
    1.84 +    0xff, 0xe1,                                                                        /*   jmp    *%ecx           */
    1.85 +  };
    1.86 +  cpu_physical_memory_rw(addr, buf, sizeof(buf), 1);
    1.87 +  fprintf(stderr, "qemu: helper at 0x%x of size %d bytes, to move kernel of %d bytes from 0x%x to 0x%x\n",
    1.88 +	  (int)addr, (int)sizeof(buf), (int)len, (int)src, (int)dst);
    1.89 +}
    1.90 +
    1.91  
    1.92  static long get_file_size(FILE *f)
    1.93  {
    1.94 @@ -597,8 +681,15 @@ static void load_linux(const char *kerne
    1.95  	    stl_p(header+0x214, reloc_prot_addr);
    1.96  	    fprintf(stderr, "qemu: kernel is relocatable\n");
    1.97  	} else {
    1.98 -	    fprintf(stderr, "qemu: unable to load non-relocatable kernel\n");
    1.99 -	    exit(1);
   1.100 +	    /* Setup a helper which moves  kernel back to
   1.101 +	     * its expected addr after firmware has got out
   1.102 +	     * of the way. We put a helper at  reloc_prot_addr+kernel_size.
   1.103 +	     * It moves kernel from reloc_prot_addr to prot_addr and
   1.104 +	     * then jumps to prot_addr. Yes this is sick.
   1.105 +	     */
   1.106 +	    fprintf(stderr, "qemu: kernel is NOT relocatable\n");
   1.107 +	    stl_p(header+0x214, reloc_prot_addr + kernel_size);
   1.108 +	    setup_relocator(reloc_prot_addr + kernel_size, reloc_prot_addr, prot_addr, kernel_size);
   1.109  	}
   1.110      }
   1.111