From 91cf57b14b267c94e8a8ffd96ab33985c9ba2c37 Mon Sep 17 00:00:00 2001
From: Alexander Graf <agraf@suse.de>
Date: Sun, 28 Mar 2010 22:34:44 +0200
Subject: [PATCH] Zipl VirtIO bootloader code

This is a patch to enable booting inside KVM with virtio hard drives.

It creates a first stage bootloader (virtio1) that reads sector descriptors
from offset 0x20 and following of the MBR. The resulting binary should be
put in the pc-bios directory of qemu.

It also adds a second stage bootloader (virtio2) that is written to the
disk by zipl. This second stage bootloader shows the boot menu and loads
the kernel and initrd.

The second stage bootloader is only written to the disk if virtio is detected.

Signed-off-by: Alexander Graf <agraf@suse.de>
---
 zipl/boot/Makefile            |   28 +++-
 zipl/boot/common.S            |  138 +++++++++----
 zipl/boot/menu.S              |   33 ++--
 zipl/boot/virtio-loadstage2.c |   51 +++++
 zipl/boot/virtio-menuparam.S  |   36 +++
 zipl/boot/virtio-transport.c  |  414 +++++++++++++++++++++++++++++++++++
 zipl/boot/virtio-transport.h  |  475 +++++++++++++++++++++++++++++++++++++++++
 zipl/boot/virtio1.S           |   30 +++
 zipl/boot/virtio2.S           |   81 +++++++
 zipl/include/boot.h           |    1 +
 zipl/include/disk.h           |    1 +
 zipl/src/boot.c               |   40 +++-
 zipl/src/bootmap.c            |   27 +++-
 zipl/src/disk.c               |   23 ++-
 zipl/src/install.c            |   37 +++-
 15 files changed, 1336 insertions(+), 79 deletions(-)
 create mode 100644 zipl/boot/virtio-loadstage2.c
 create mode 100644 zipl/boot/virtio-menuparam.S
 create mode 100644 zipl/boot/virtio-transport.c
 create mode 100644 zipl/boot/virtio-transport.h
 create mode 100644 zipl/boot/virtio1.S
 create mode 100644 zipl/boot/virtio2.S

diff --git a/zipl/boot/Makefile b/zipl/boot/Makefile
index f72a306..68ccaf2 100644
--- a/zipl/boot/Makefile
+++ b/zipl/boot/Makefile
@@ -3,10 +3,11 @@ include ../../common.mak
 
 S390_TOOLS_VERSION_EBCDIC := $(shell echo -n $(S390_TOOLS_RELEASE) | dd conv=ebcdic | hexdump -v -e '/1 "0x%02x,"' | sed 's/.$$//')
 
-CFLAGS  += -I../include -D__ASSEMBLY__ \
+CFLAGS  += -I../include -D__ASSEMBLY__ -m64 \
 		-DS390_TOOLS_VERSION_EBCDIC=$(S390_TOOLS_VERSION_EBCDIC)
-LDFLAGS = -O2
+LDFLAGS = -Os -m64
 FILES	:= fba0.bin fba2.bin eckd0.bin eckd1a.bin eckd1b.bin eckd2.bin \
+	   virtio1.bin virtio2.bin \
 	   eckd2dump.bin tapedump.bin stage3.bin tape0.bin fba2dump.bin \
 	   eckd2mvdump.bin
 
@@ -15,10 +16,30 @@ all: data.o data.h tape0.bin
 # Prevent make from using some default rules...
 %:	%.S
 
+virtio-transport.o:	virtio-transport.c
+	echo "$(CC) $(CFLAGS) -Os -g -c -fno-stack-protector -o $@ $<";   \
+	$(CC) $(CFLAGS) -Os -g -c -fno-stack-protector -o $@ $<
+
+virtio-loadstage2.o:	virtio-loadstage2.c
+	echo "$(CC) $(CFLAGS) -Os -g -c -fno-stack-protector -o $@ $<";   \
+	$(CC) $(CFLAGS) -Os -g -c -fno-stack-protector -o $@ $<
+
 %.o:	%.S
 	echo "$(CC) $(CFLAGS) -c -o $@ $<";                        \
 	$(CC) $(CFLAGS) -c -o $@ $<
 
+virtio2.exec:	virtio2.o virtio-transport.o virtio-menuparam.o
+	echo "$(CC) $(LDFLAGS) -o $@ $$SFLAGS $<"
+	$(CC) $(LDFLAGS) -o virtio2.exec virtio2.o virtio-transport.o \
+			 virtio-menuparam.o \
+			 -nostdlib -Wl,-Ttext,0x2000,-Tbss,0x4500
+
+virtio1.exec:	virtio1.o virtio-transport.o virtio-loadstage2.o
+	echo "$(CC) $(LDFLAGS) -o $@ $$SFLAGS $<"
+	$(CC) $(LDFLAGS) -o virtio1.exec virtio1.o virtio-transport.o \
+			 virtio-loadstage2.o \
+			 -nostdlib -Wl,-Ttext,0x1000,-Tbss,0x4000
+
 %.exec:	%.o
 	@STAGE=$$(                                  \
 		echo $@ | awk '                           \
@@ -39,7 +60,7 @@ all: data.o data.h tape0.bin
 	$(CC) $(LDFLAGS) -o $@ $$SFLAGS $<
 
 %.bin:	%.exec
-	$(OBJCOPY) -O binary --only-section=.text $< $@
+	$(OBJCOPY) -O binary --only-section=.text --only-section=.rodata $< $@
 
 data.o: $(FILES)
 	$(LD) -r -b binary -o data.o $(FILES)
@@ -59,6 +80,7 @@ clean:
 
 eckd2.o: common.S menu.S sclp.S
 fba2.o: common.S menu.S sclp.S
+virtio2.o: common.S menu.S virtio-transport.o
 eckd2dump.o: dumpcommon.S
 tapedump.o: dumpcommon.S
 eckd2mvdump.o: dumpcommon.S
diff --git a/zipl/boot/common.S b/zipl/boot/common.S
index 108dbbf..9d814c7 100644
--- a/zipl/boot/common.S
+++ b/zipl/boot/common.S
@@ -16,6 +16,59 @@ EENABLE_DEV  = 0x00000100 # enable device failed
 EDISABLE_DEV = 0x00000101 # disable device failed
 ESSCH        = 0x00000102 # start subchannel failed
 EWRONGTYPE   = 0x00000200 # wrong ipl type supplied
+
+#ifdef SCSI
+
+/*
+ * SCSI descriptors are 128 bytes long, containing two 64-bit parameters.
+ * So we need to massage the code a bit to allow handlers to be 64-bit
+ * aware and make use of the long desciptors.
+ */
+
+#define DESC_SHIFT	4
+#define DESC_LEN	16
+
+#else
+
+#define DESC_SHIFT	3
+#define DESC_LEN	8
+
+#endif
+
+/* If our user is 64 bit aware, we need to use 64 bit instructions and stack. */
+#ifdef BIT64
+
+/*
+ * The following instruction defines ensure we get 64 bit values while
+ * traversing the block identifiers which have the following layout:
+ *
+ *    ______________________________________________________
+ *   |                    |           |           |         |
+ *   |     u64 sector     |  u16 size | u16 count | u32 pad |
+ *   |                    |           |           |         |
+ *   '------------------------------------------------------'
+ *
+ */
+#define LM		lmg
+#define LR		lgr
+
+/* Our C code expects to be able to use stmg to the stack, so give it enough
+   space */
+#define FRAME_SIZE	(24 * 8)
+
+/* We still use lm and stm to access the stack, so let's also use the old
+   offsets */
+#define FRAME_R6	(6 * 4)
+
+#else
+
+#define LM		lm
+#define LR		lr
+#define FRAME_SIZE	(24 * 4)
+#define FRAME_R6	(6 * 4)
+
+#endif
+
 	.macro stage2_start
 #
 # Function entry point at 0x2000 is called with C linkage
@@ -36,7 +89,7 @@ _start: stm    %r0,%r15,0x180           # store all registers
 0:      l      %r15,3f-0b(%r13)         # load end of stack address
 
 	bas    %r14,_menu-0b(%r13)	# show menu
-	sll    %r2,3			# get offset from config number
+	sll    %r2,DESC_SHIFT		# get offset from config number
 	lr     %r6,%r2
 
         l      %r11,0xb8                # load ipl device subchannel id
@@ -48,18 +101,18 @@ _start: stm    %r0,%r15,0x180           # store all registers
 6:
         la     %r10,0
         st     %r10,__LC_PANIC_MAGIC    # clear panic flag
-	lm     %r2,%r3,STAGE2_DESC      # get block with configurations
+	LM     %r2,%r3,STAGE2_DESC      # get block with configurations
 	lr     %r4,%r11
 	l      %r5,4f-0b(%r13)
 	la     %r12,0(%r5)              # FIXME: provide the correct offset
         bas    %r14,_load_direct-0b(%r13)
 	ar     %r6,%r12
-        lm     %r2,%r3,8(%r6)           # get block with load descriptors
+        LM     %r2,%r3,DESC_LEN(%r6)    # get block with load descriptors
         lr     %r4,%r11
         lr     %r5,%r12                 # set load address
         bas    %r14,_load_direct-0b(%r13)
 	la     %r12,32(%r12)		# skip header entry
-1:      lm     %r2,%r3,0(%r12)		# load blocklist container of descriptor
+1:      LM     %r2,%r3,0(%r12)		# load blocklist container of descriptor
 	l      %r4,20(%r12)		# load type
 	lhi    %r5,0xf
 	nr     %r4,%r5			# only the last four bits are the type
@@ -71,7 +124,7 @@ _start: stm    %r0,%r15,0x180           # store all registers
         bas    %r14,0(%r1)
 2:	la     %r12,32(%r12)		# next load descriptor
         b      1b-0b(%r13)
-3:      .long  0x10000-96
+3:      .long  0x10000-FRAME_SIZE
 4:	.long  0x8000
 5:	.long  _bug			# type 0: not allowed
 	.long  _execute			# type 1: jump to target
@@ -86,12 +139,12 @@ _bug:
 
 
 _load_binary:
-        stm    %r6,%r15,24(%r15)
+        stm    %r6,%r15,FRAME_R6(%r15)
         basr   %r13,0                   # base register
 0:      s      %r15,1f-0b(%r13)         # create stack frame
         lr     %r12,%r4
 	bas    %r14,_load_blocklist-0b(%r13)
-        lm     %r6,%r15,120(%r15)
+        lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)
         br     %r14
 1:	.long  96
 	
@@ -123,56 +176,59 @@ _execute:
 #   %r4    : device subchannel id
 #   %r5    : load address
 _load_blocklist:
-        stm    %r6,%r15,24(%r15)
+        stm    %r6,%r15,FRAME_R6(%r15)
         basr   %r13,0                   # base register
 0:      s      %r15,7f-0b(%r13)         # create stack frame
-1:	lr     %r12,%r4                 # save device subchannel id
-	lr     %r11,%r5                 # save load address
-	lr     %r8,%r2                  # save descriptor
-	lr     %r9,%r3
+1:	LR     %r12,%r4                 # save device subchannel id
+	LR     %r11,%r5                 # save load address
+	LR     %r8,%r2                  # save descriptor
+	LR     %r9,%r3
 	bas    %r14,_extract_length-0b(%r13)   # get length from descriptor
-	lr     %r10,%r2                 # save returned length
-	lr     %r2,%r8                  # reload descriptor to %r2/%r3
-	lr     %r3,%r9
-	lr     %r4,%r12                 # reload device id to %r4
+	LR     %r10,%r2                 # save returned length
+	LR     %r2,%r8                  # reload descriptor to %r2/%r3
+	LR     %r3,%r9
+	LR     %r4,%r12                 # reload device id to %r4
 	l      %r5,9f-0b(%r13)          # get memory area for indirect block
         bas    %r14,_load_direct-0b(%r13) # load indirect block
-	lr     %r9,%r10                 # (length / 8 - 1) = # direct descr.
-	srl    %r9,3
+	LR     %r9,%r10                 # (length / 8 - 1) = # direct descr.
+	srl    %r9,DESC_SHIFT
 	bctr   %r9,0
 	l      %r8,9f-0b(%r13)
-2:	clc    0(8,%r8),8f-0b(%r13)     # test block descriptor
+2:	clc    0(DESC_LEN,%r8),8f-0b(%r13) # test block descriptor
 	be     6f-0b(%r13)              # descriptor == 0 -> done
-	lm     %r2,%r3,0(%r8)           # pass block descriptor
+	LM     %r2,%r3,0(%r8) # pass block descriptor
 	bas    %r14,_is_zero_block-0b(%r13)    # test for special 0 descriptor
 	ltr    %r2,%r2                  # is it a hole to fill with 0?
 	bne    4f-0b(%r13)              # no, normal block descriptor
-	lm     %r2,%r3,0(%r8)           # pass block descriptor
+	LM     %r2,%r3,0(%r8) # pass block descriptor
 	bas    %r14,_extract_length-0b(%r13)   # get length from descriptor
-	lr     %r3,%r2                  # move length to an odd register
-	lr     %r2,%r11                 # move address to an even register
+	LR     %r3,%r2                  # move length to an odd register
+	LR     %r2,%r11                 # move address to an even register
 	alr    %r11,%r3                 # add length to load address
 	slr    %r5,%r5                  # set length of source to zero
 3:	mvcle  %r2,%r4,0                # clear storage
 	bo     3b-0b(%r13)	
 	b      5f-0b(%r13)
-4:	lm     %r2,%r3,0(%r8)           # pass block descriptor
-	lr     %r4,%r12                 # pass device subchannel id
-        lr     %r5,%r11                 # pass load address
+4:	LM     %r2,%r3,0(%r8) # pass block descriptor
+	LR     %r4,%r12                 # pass device subchannel id
+        LR     %r5,%r11                 # pass load address
         bas    %r14,_load_direct-0b(%r13) # load indirect block
-	lr     %r11,%r2                 # move updated load address
-5:	la     %r8,8(%r8)               # next descriptor
+	LR     %r11,%r2                 # move updated load address
+5:	la     %r8,DESC_LEN(%r8)        # next descriptor
 	bct    %r9,2b-0b(%r13)
-	lm     %r2,%r3,0(%r8)           # load continuation descriptor
-	lr     %r4,%r12                 # move device id for next round
-	lr     %r5,%r11			# move load address for next round
-	clc    0(8,%r8),8f-0b(%r13)     # test continuation descriptor
+	LM     %r2,%r3,0(%r8) # load continuation descriptor
+	LR     %r4,%r12                 # move device id for next round
+	LR     %r5,%r11			# move load address for next round
+	clc    0(DESC_LEN,%r8),8f-0b(%r13) # test continuation descriptor
 	bne    1b-0b(%r13)              # != 0 -> load next indirect block
-6:      lr     %r2,%r11                 # return updated load address
-	lm     %r6,%r15,120(%r15)
+6:      LR     %r2,%r11                 # return updated load address
+	lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)
         br     %r14
 7:      .long  96
 8:      .long  0,0
+#if DESC_LEN == 16
+        .long  0,0
+#endif
 9:      .long  0x8200                   # memory address for indirect blocks
 
         .endm
@@ -185,7 +241,7 @@ _load_blocklist:
 #   %r2 : lpum of subchannel
 #
 _enable_device:
-        stm    %r6,%r15,24(%r15)
+        stm    %r6,%r15,FRAME_R6(%r15)
         basr   %r13,0                   # base register
 0:      s      %r15,4f-0b(%r13)
 	lr     %r1,%r2
@@ -201,7 +257,7 @@ _enable_device:
 	lctl   %c6,%c6,6f-0b(%r13)      # enable all interrupt classes
 	sr     %r2,%r2
 	ic     %r2,10+5f-0b(%r13)	# return lpum in r2
-        lm     %r6,%r15,120(%r15)
+        lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)
         br     %r14
 2: # clear status and retry
 	tsch   __LC_IRB
@@ -220,7 +276,7 @@ _enable_device:
 #   %r2 : device subchannel id
 #
 _disable_device:
-        stm    %r6,%r15,24(%r15)
+        stm    %r6,%r15,FRAME_R6(%r15)
         basr   %r13,0                   # base register
 0:      s      %r15,4f-0b(%r13)
 	lr     %r1,%r2
@@ -234,7 +290,7 @@ _disable_device:
         msch   5f-0b(%r13)
 	brc    6,2f			# status pending or busy
 	brc    1,3f			# panic if not operational
-        lm     %r6,%r15,120(%r15)
+        lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)
         br     %r14
 2: # clear status and retry
 	tsch   __LC_IRB
@@ -295,10 +351,10 @@ _wait_for_int:
 #   %r4 : address of irb
 #
 _ssch:
-	stm    %r6,%r15,24(%r15)
+	stm    %r6,%r15,FRAME_R6(%r15)
 	basr   %r13,0			# get base register
 0:
-	ahi    %r15,-96			# create stack frame
+	ahi    %r15,-FRAME_SIZE		# create stack frame
 	lr     %r6,%r2			# r6:  sid
 	lr     %r7,%r3			# r7:  orb
 	lr     %r8,%r4			# r8:  irb
@@ -334,7 +390,7 @@ _ssch:
 	jnz    6f			# if status!=0 goto retry
 	tm     8(%r8),0x4		# test irb device end
 	jz     4b			# if !device_end goto wait_for_int_loop
-	lm     %r6,%r15,120(%r15)
+	lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)
 	br     %r14			# return
 
 6: # retry
diff --git a/zipl/boot/menu.S b/zipl/boot/menu.S
index 40c8d17..36210f4 100644
--- a/zipl/boot/menu.S
+++ b/zipl/boot/menu.S
@@ -19,6 +19,7 @@ MENU_PARAM_TIMEOUT	= 2			# offset of timeout parameter
 MENU_PARAM_BANNER	= 4			# offset of banner parameter
 MENU_PARAM_CONFIG	= 6			# offset of config parameter
 
+#ifndef VIRTIO
 
 #include "sclp.S"
 
@@ -28,6 +29,8 @@ MENU_PARAM_CONFIG	= 6			# offset of config parameter
 	sclp_read_info
 	sclp_param
 
+#endif
+
 #
 # Subroutine which implements an interactive boot menu.
 #
@@ -36,12 +39,12 @@ MENU_PARAM_CONFIG	= 6			# offset of config parameter
 #
 
 _menu:
-	stm    %r6,%r15,24(%r15)		# save registers
+	stm    %r6,%r15,FRAME_R6(%r15)		# save registers
 	stidp  LC_CPUID				# need CPUID for VM check
 
 	basr   %r13,0				# get base register
 .LbaseM1:
-	ahi    %r15,-96				# create stack frame
+	ahi    %r15,-FRAME_SIZE			# create stack frame
 
 	l      %r6,.LparmareaM0-.LbaseM1(%r13)	# get menu param area address
 
@@ -184,7 +187,7 @@ _menu:
 
 .LendM1:
 	lr     %r2,%r8				# get menu result
-	lm     %r6,%r15,120(%r15)		# restore registers
+	lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)	# restore registers
 
 	br     %r14
 
@@ -198,11 +201,11 @@ _menu:
 #
 
 _menu_list:
-	stm    %r6,%r15,24(%r15)		# save registers
+	stm    %r6,%r15,FRAME_R6(%r15)		# save registers
 
 	basr   %r13,0				# get base register
 .LbaseM2:
-	ahi    %r15,-96				# create stack frame
+	ahi    %r15,-FRAME_SIZE			# create stack frame
 
 	l      %r8,.LparmareaM0-.LbaseM2(%r13)	# get menu param area address
 	la     %r6,63				# number of configs
@@ -230,7 +233,7 @@ _menu_list:
 	la     %r2,.Ltext5M0-.LbaseM2(%r13)	# print empty line
 	bras   %r14,_sclp_print
 
-	lm     %r6,%r15,120(%r15)		# restore registers
+	lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)	# restore registers
 
 	br     %r14
 
@@ -240,11 +243,11 @@ _menu_list:
 #
 
 _menu_prompt:
-	stm    %r6,%r15,24(%r15)		# save registers
+	stm    %r6,%r15,FRAME_R6(%r15)		# save registers
 
 	basr   %r13,0				# get base register
 .LbaseM3:
-	ahi    %r15,-96				# create stack frame
+	ahi    %r15,-FRAME_SIZE			# create stack frame
 
 	l      %r2,.LparmareaM0-.LbaseM3(%r13)	# get menu param area address
 	lh     %r0,MENU_PARAM_TIMEOUT(%r2)	# get timeout value
@@ -300,7 +303,7 @@ _menu_prompt:
 
 	bras   %r14,_sclp_print			# print prompt
 
-	lm     %r6,%r15,120(%r15)		# restore registers
+	lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)	# restore registers
 
 	br     %r14
 
@@ -316,11 +319,11 @@ _menu_prompt:
 #
 
 _menu_read:
-	stm    %r6,%r15,24(%r15)		# save registers
+	stm    %r6,%r15,FRAME_R6(%r15)		# save registers
 
 	basr   %r13,0				# get base register
 .LbaseM4:
-	ahi    %r15,-96				# create stack frame
+	ahi    %r15,-FRAME_SIZE			# create stack frame
 
 	l      %r6,.LparmareaM0-.LbaseM4(%r13)	# get menu param area address
 	l      %r7,.LtempareaM0-.LbaseM4(%r13)	# get temp area = input area
@@ -392,7 +395,7 @@ _menu_read:
 	sr     %r2,%r2				# set default configuration
 
 .LendM4:
-	lm     %r6,%r15,120(%r15)		# restore registers
+	lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)	# restore registers
 
 	br     %r14
 
@@ -508,11 +511,11 @@ _menu_etoi:
 #
 
 _menu_param:
-	stm    %r6,%r15,24(%r15)		# save registers
+	stm    %r6,%r15,FRAME_R6(%r15)		# save registers
 
 	basr   %r13,0				# get base register
 .LbaseM7:
-	ahi    %r15,-96				# create stack frame
+	ahi    %r15,-FRAME_SIZE			# create stack frame
 
 	la     %r6,.LparamM7-.LbaseM7(%r13)	# parameter area
 
@@ -563,7 +566,7 @@ _menu_param:
 	lhi    %r2,2				# failure
 
 .LendM7:
-	lm     %r6,%r15,120(%r15)		# restore registers
+	lm     %r6,%r15,(FRAME_SIZE + FRAME_R6)(%r15)	# restore registers
 
 	br     %r14
 
diff --git a/zipl/boot/virtio-loadstage2.c b/zipl/boot/virtio-loadstage2.c
new file mode 100644
index 0000000..007fd81
--- /dev/null
+++ b/zipl/boot/virtio-loadstage2.c
@@ -0,0 +1,51 @@
+/*
+ * S390 stage 2 loading program
+ *
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "virtio-transport.h"
+
+unsigned long _sclp_setup(unsigned long deactivate);
+unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
+				 ulong subchan_id, void *load_addr);
+unsigned long _sclp_print(char *ebcdic);
+
+void virtio_load_stage2() {
+    u64 *sector = (void*)0x8020;
+    char *stage2 = (void*)0x2000;
+    void (*stage2_func)(void) = (void*)0x2008;
+
+    /* Initialize virtio and fetch MBR to 0x8000 */
+    _sclp_setup(0);
+
+    /* Read in stage2 */
+    while (*sector) {
+        stage2 = (void*)virtio_load_direct(sector[0], sector[1], 0, stage2);
+        sector += 2;
+    }
+
+    /* Newline so stage2 starts off fresh */
+    _sclp_print("");
+
+    /* Call stage2! */
+    stage2_func();
+}
diff --git a/zipl/boot/virtio-menuparam.S b/zipl/boot/virtio-menuparam.S
new file mode 100644
index 0000000..c95147b
--- /dev/null
+++ b/zipl/boot/virtio-menuparam.S
@@ -0,0 +1,36 @@
+/*
+ * Menu param rodata relocation
+ *
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/*
+ * The last object needs to be the menu list, so zipl can fill it for us.
+ *
+ * This file gets included as the last one in the build, giving us exactly
+ * that.
+ */
+
+.section .rodata
+        .align 2
+.byte 0xde, 0xad
+.global .Lmenuparam
+.Lmenuparam:
diff --git a/zipl/boot/virtio-transport.c b/zipl/boot/virtio-transport.c
new file mode 100644
index 0000000..182aa2c
--- /dev/null
+++ b/zipl/boot/virtio-transport.c
@@ -0,0 +1,414 @@
+/*
+ * VirtIO transport implementation
+ *
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "virtio-transport.h"
+
+/***********************************************
+ *             Virtio functions                *
+ ***********************************************/
+
+static struct virtio_dev console_dev;
+static struct vring console_in, console_out;
+static char console_works = 0;
+
+static struct vring block;
+static char block_works = 0;
+
+static u32 boot_map;
+
+void virtio_stop(void)
+{
+    while(1)
+        early_puts("stop\n");
+}
+
+void virtio_panic(const char *string)
+{
+    early_puts("panic: ");
+    early_puts(string);
+    early_puts("\n");
+
+    virtio_stop();
+}
+
+static void virtio_puts(const char *string)
+{
+    int string_len = 0;
+    const char *ns;
+
+    if (!console_works) {
+        early_puts("early: ");
+        early_puts(string);
+        return;
+    }
+
+    for(ns = string; *ns; ns++)
+        string_len++;
+
+    vring_send_buf(&console_out, (void*)string, string_len, 0);
+    vring_wait_reply(&console_out, 0);
+}
+
+static void virtio_enum_console(struct virtio_dev *vdev)
+{
+    console_dev = *vdev;
+    struct virtio_vqconfig *vq_in = &vdev->vqconfig[0];
+    struct virtio_vqconfig *vq_out = &vdev->vqconfig[1];
+
+    /* We always take the first console */
+    if (console_works)
+        return;
+
+    /* safety check */
+    if (vdev->header->num_vq < 2)
+        return;
+
+    vring_init(&console_in, vq_in->num, (void*)(ulong)vq_in->address,
+               KVM_S390_VIRTIO_RING_ALIGN);
+    vring_init(&console_out, vq_out->num, (void*)(ulong)vq_out->address,
+               KVM_S390_VIRTIO_RING_ALIGN);
+
+    vdev->header->status = VIRTIO_CONFIG_S_DRIVER_OK;
+    virtio_set_status((unsigned long)vdev->header);
+
+    console_works = 1;
+}
+
+static void virtio_enum_block(struct virtio_dev *vdev)
+{
+    console_dev = *vdev;
+    struct virtio_vqconfig *vq = &vdev->vqconfig[0];
+
+    /* We always only use the first block device */
+    if (block_works)
+        return;
+
+    /* safety check */
+    if (vdev->header->num_vq != 1)
+        return;
+
+    vring_init(&block, vq->num, (void*)(ulong)vq->address,
+               KVM_S390_VIRTIO_RING_ALIGN);
+
+    vdev->header->status = VIRTIO_CONFIG_S_DRIVER_OK;
+    virtio_set_status((unsigned long)vdev->header);
+
+    block_works = 1;
+}
+
+/*
+ * Checks if we can make use of the device.
+ *
+ * Returns the next device pointer or NULL if done
+ */
+static void *virtio_enum_dev(void *devptr)
+{
+    struct virtio_dev_header *dev = devptr;
+    struct virtio_dev vdev;
+
+    if (!dev->type)
+        return NULL;
+
+    debug_print_addr("enum ptr", devptr);
+    debug_print_int("type", dev->type);
+    debug_print_int("num_vq", dev->num_vq);
+    debug_print_int("feature_len", dev->feature_len);
+    debug_print_int("config_len", dev->config_len);
+
+    vdev.header = dev;
+    vdev.vqconfig = (struct virtio_vqconfig*)dev->vqconfig;
+    vdev.host_features = (char*)&vdev.vqconfig[dev->num_vq];
+    vdev.guest_features = &vdev.host_features[dev->feature_len];
+    vdev.config = &vdev.guest_features[dev->feature_len];
+
+    switch (dev->type) {
+        case VIRTIO_ID_CONSOLE:
+            //virtio_reset(dev);
+            virtio_enum_console(&vdev);
+            break;
+        case VIRTIO_ID_BLOCK:
+            //virtio_reset(dev);
+            virtio_enum_block(&vdev);
+            break;
+    }
+
+    return &vdev.config[dev->config_len];
+}
+
+static int virtio_read(ulong sector, void *load_addr)
+{
+    struct virtio_blk_outhdr out_hdr;
+    u8 status;
+
+    /* Tell the host we want to read */
+    out_hdr.type = VIRTIO_BLK_T_IN;
+    out_hdr.ioprio = 99;
+    out_hdr.sector = sector;
+
+    vring_send_buf(&block, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT);
+
+    /* This is where we want to receive data */
+    vring_send_buf(&block, load_addr, SECTOR_SIZE, VRING_DESC_F_WRITE |
+                   VRING_HIDDEN_IS_CHAIN | VRING_DESC_F_NEXT);
+
+    /* status field */
+    vring_send_buf(&block, &status, sizeof(u8), VRING_DESC_F_WRITE |
+                   VRING_HIDDEN_IS_CHAIN);
+
+    /* Now we can tell the host to read */
+    vring_wait_reply(&block, 0);
+
+    return status;
+}
+
+static void virtio_init(void)
+{
+    long ram_size = sclp_get_ram_size();
+    void *virtio_start = (void*)ram_size;
+    u64 *stage2_desc = (void*)0x218;
+    u64 *tmp_space = (void*)0x8000;
+
+    debug_print_int("ram size", ram_size);
+
+    block_works = 0;
+    console_works = 0;
+
+    /* Initialize all subsystems */
+
+    do {
+        virtio_start = virtio_enum_dev(virtio_start);
+    } while(virtio_start);
+
+    /* Tell common.S about the boot map */
+    /* Get MBR */
+    virtio_read(0, tmp_space);
+    /* Copy boot map identifier to stage2_desc */
+    boot_map = tmp_space[5];
+    stage2_desc[0] = tmp_space[2];
+    stage2_desc[1] = tmp_space[3];
+
+    return;
+}
+
+/***********************************************
+ *            Assembly callbacks               *
+ ***********************************************/
+
+/*
+ * parameter
+ *   %r2+%r3: blocklist descriptor
+ * returns
+ *   %r2    : number of bytes (blocksize * number of blocks)
+ */
+unsigned long virtio_extract_length(ulong rec_list1, ulong rec_list2)
+{
+    unsigned long retval;
+    early_puts("_extract_length\n");
+
+    debug_print_int("rec_list1", rec_list1);
+    debug_print_int("rec_list2", rec_list2);
+
+    retval = (((rec_list2 >> 32) + 1) & 0xffff) * (rec_list2 >> 48);
+
+    debug_print_int("length", retval);
+    return retval;
+}
+
+/*
+ * parameter
+ *   %r2+%r3: blocklist descriptor
+ * returns
+ *   %r2    : == 0 for normal block descriptor
+ *            != 0 for zero block descriptor
+ */
+unsigned long virtio_is_zero_block(ulong rec_list1, ulong rec_list2)
+{
+    early_puts("_is_zero_block\n");
+
+    debug_print_int("rec_list1", rec_list1);
+    debug_print_int("rec_list2", rec_list2);
+
+    return rec_list1;
+}
+
+/*
+ * parameter
+ *   %r2+%r3:	 recordlist descriptor  CCHHRLLn
+ *   %r4    :	 device subchannel id
+ *   %r5    :	 load address
+ */
+unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
+				 ulong subchan_id, void *load_addr)
+{
+    u8 status;
+    int sec = rec_list1;
+    int sec_num = (((rec_list2 >> 32)+ 1) & 0xffff);
+    int sec_len = rec_list2 >> 48;
+    ulong addr = (ulong)load_addr;
+    int i;
+
+    early_puts("_load_direct\n");
+    debug_print_int("rec_list1", rec_list1);
+    debug_print_int("rec_list2", rec_list2);
+    debug_print_int("subchan_id", subchan_id);
+    debug_print_addr("load_addr", load_addr);
+
+    if (sec_len != SECTOR_SIZE) {
+        virtio_panic("sector size invalid");
+    }
+
+    for (i = 0; i < sec_num; i++) {
+        if ((i % 100) == 0) {
+            virtio_puts(".");
+        }
+        status = virtio_read(sec, (void*)addr);
+
+        if (status) {
+            debug_print_int("load_direct status", status);
+            virtio_panic("I/O Error");
+        }
+        sec++;
+        addr += SECTOR_SIZE;
+    }
+
+    return addr;
+}
+
+/***********************************************
+ *                 SCLP fake                   *
+ ***********************************************/
+
+
+/*
+ * Subroutine to set up the SCLP interface.
+ *
+ * Parameters:
+ *   R2  = 0 to activate, non-zero to deactivate
+ *
+ * Returns:
+ *   R2  = 0 on success, non-zero on failure
+ */
+unsigned long _sclp_setup(unsigned long deactivate)
+{
+    if (deactivate)
+        return 0;
+
+    early_puts("_sclp_setup\n");
+    virtio_init();
+    return 0;
+}
+
+/*
+ * Subroutine which prints a given text to the SCLP console.
+ *
+ * Parameters:
+ *   R2  = address of nil-terminated EBCDIC text
+ *
+ * Returns:
+ *   R2  = 0 on success, 1 on failure
+ */
+unsigned long _sclp_print(unsigned char *ebcdic)
+{
+    unsigned char *ns;
+    unsigned char out[] = " ";
+
+    /* Invalid pointer */
+    if ((unsigned long)ebcdic & 0xffff0000)
+        return 1;
+
+    /* Control character */
+    if (e2a[*ebcdic] == 0x07)
+        return 1;
+
+    /* Print it out */
+    for (ns = ebcdic; *ns; ns++) {
+        out[0] = e2a[*ns];
+        virtio_puts((char*)out);
+    }
+
+    virtio_puts("\r\n");
+
+    return 0;
+}
+
+/*
+ * Subroutine which reads text from the SCLP console.
+ *
+ * Parameters:
+ *   R2  = 0 for no timeout, non-zero for timeout in (approximated) seconds
+ *   R3  = destination address for input
+ *
+ * Returns:
+ *   R2  = 0 on input, 1 on failure, 2 on timeout
+ *   R3  = length of input data
+ */
+unsigned long virtio_sclp_read(unsigned long timeout, char *p)
+{
+    char t[] = " \n";
+    int i;
+    int r;
+
+    debug_print_int("read timeout", timeout);
+    virtio_puts("-> ");
+    vring_send_buf(&console_in, t, 1, VRING_DESC_F_WRITE);
+    r = vring_wait_reply(&console_in, timeout);
+
+    /* timeout while reading */
+    if (r) {
+        return 2;
+    }
+
+    virtio_puts(t);
+
+    /* The caller expects EBCDIC, not ASCII */
+    for (i = 0; i < sizeof(e2a); i++) {
+        if (e2a[i] == t[0]) {
+            p[0] = i;
+
+            debug_print_int("ebcdic input", i);
+            return 0;
+        }
+    }
+
+    virtio_puts("E: e2a\n");
+
+    /* Character not found in the translation table */
+    return 1;
+}
+
+/*
+ * Subroutine to read load parameter.
+ *
+ * Parameters:
+ *   R2  = destination address for load parameter (8 bytes)
+ *
+ * Returns:
+ *   R2  = 0 on success, 1 on failure
+ */
+unsigned long _sclp_param(u64 *dst)
+{
+    early_puts("_sclp_param\n");
+    *dst = 1;
+    return 0;
+}
diff --git a/zipl/boot/virtio-transport.h b/zipl/boot/virtio-transport.h
new file mode 100644
index 0000000..149d1e2
--- /dev/null
+++ b/zipl/boot/virtio-transport.h
@@ -0,0 +1,475 @@
+/*
+ * VirtIO transport helpers
+ *
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// #define DEBUG
+
+#define u8	unsigned char
+#define u16	unsigned short
+#define u32	unsigned int
+#define u64	unsigned long long
+#define ulong	unsigned long
+typedef long size_t;
+
+#define EIO	1
+#define EBUSY	2
+#define NULL    0
+
+static inline void *memset(void *s, int c, size_t n)
+{
+    int i;
+    unsigned char *p = s;
+
+    for (i = 0; i < n; i++) {
+        p[i] = c;
+    }
+
+    return s;
+}
+
+static const unsigned char e2a[] = {
+    0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F,
+    0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07,
+    0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+    0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B,
+    0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07,
+    0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04,
+    0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A,
+    0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86,
+    0x87, 0xA4, 0x5B, 0x2E, 0x3C, 0x28, 0x2B, 0x21,
+    0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07,
+    0x8D, 0xE1, 0x5D, 0x24, 0x2A, 0x29, 0x3B, 0x5E,
+    0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F,
+    0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
+    0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+    0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
+    0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+    0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1,
+    0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
+    0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07,
+    0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+    0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07,
+    0x9B, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC,
+    0xAB, 0x07, 0xAA, 0x7C, 0x07, 0x07, 0x07, 0x07,
+    0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07,
+    0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
+    0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98,
+    0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+    0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07,
+};
+
+static inline void fill_hex(char *out, unsigned char val)
+{
+    const char hex[] = "0123456789abcdef";
+
+    out[0] = hex[(val >> 4) & 0xf];
+    out[1] = hex[val & 0xf];
+}
+
+#ifdef DEBUG
+static void early_puts(const char *string);
+#endif
+
+static inline void debug_print_int(const char *desc, u64 addr)
+{
+#ifdef DEBUG
+    unsigned char *addr_c = (unsigned char*)&addr;
+    char out[] = ": 0xffffffffffffffff\n";
+    int i;
+
+    for (i = 0; i < sizeof(addr); i++) {
+        fill_hex(&out[4 + (i*2)], addr_c[i]);
+    }
+
+    early_puts(desc);
+    early_puts(out);
+#endif
+}
+
+static inline void debug_print_addr(const char *desc, void *p)
+{
+#ifdef DEBUG
+    debug_print_int(desc, (unsigned int)(unsigned long)p);
+#endif
+}
+
+/***********************************************
+ *              SCLP functions                 *
+ ***********************************************/
+
+struct sccb_header {
+    u16     length;
+    u8      function_code;
+    u8      control_mask[3];
+    u16     response_code;
+} __attribute__((packed));
+
+struct read_info_sccb {
+    struct  sccb_header header;     /* 0-7 */
+    u16     rnmax;                  /* 8-9 */
+    u8      rnsize;                 /* 10 */
+    u8      _reserved0[24 - 11];    /* 11-15 */
+    u8      loadparm[8];            /* 24-31 */
+    u8      _reserved1[48 - 32];    /* 32-47 */
+    u64     facilities;             /* 48-55 */
+    u8      _reserved2[84 - 56];    /* 56-83 */
+    u8      fac84;                  /* 84 */
+    u8      _reserved3[91 - 85];    /* 85-90 */
+    u8      flags;                  /* 91 */
+    u8      _reserved4[100 - 92];   /* 92-99 */
+    u32     rnsize2;                /* 100-103 */
+    u64     rnmax2;                 /* 104-111 */
+    u8      _reserved5[4096 - 112]; /* 112-4095 */
+} __attribute__((packed, aligned(8)));
+
+#define SCLP_CMDW_READ_SCP_INFO         0x00020001
+#define INS_SERVC ".insn rre,0xb2200000,"
+
+/* Perform service call. Returns 0 on success. */
+static inline int sclp_servc(ulong command, void *sccb)
+{
+    int cc;
+
+    asm volatile(
+        INS_SERVC "%1,%2\n"
+        "ipm     %0\n"
+        "srl     %0,28"
+        : "=&d" (cc)
+	: "d" (command), "a" (sccb)
+        : "cc", "memory");
+
+    if (cc == 3) return -EIO;
+    if (cc == 2) return -EBUSY;
+
+    return 0;
+}
+
+static inline long sclp_get_ram_size(void)
+{
+    struct read_info_sccb sccb;
+    int rc;
+
+    memset(&sccb, 0, sizeof(sccb));
+    sccb.header.length = sizeof(sccb);
+    sccb.header.function_code = 0x80;
+    sccb.header.control_mask[2] = 0x80;
+    rc = sclp_servc(SCLP_CMDW_READ_SCP_INFO, &sccb);
+
+    if (rc)
+        return -1;
+
+    if (sccb.header.response_code != 0x10)
+        return -1;
+
+    return sccb.rnmax << 20;
+}
+
+/***********************************************
+ *           Hypercall functions               *
+ ***********************************************/
+
+#define KVM_S390_VIRTIO_NOTIFY		0
+#define KVM_S390_VIRTIO_RESET		1
+#define KVM_S390_VIRTIO_SET_STATUS	2
+
+static inline long kvm_hypercall(unsigned long nr, unsigned long param)
+{
+	register ulong r_nr asm("1") = nr;
+	register ulong r_param asm("2") = param;
+	register long retval asm("2");
+
+	asm volatile ("diag 2,4,0x500"
+		      : "=d" (retval)
+		      : "d" (r_nr), "0" (r_param)
+		      : "memory", "cc");
+
+	return retval;
+}
+
+static inline void virtio_set_status(unsigned long dev_addr)
+{
+    kvm_hypercall(KVM_S390_VIRTIO_SET_STATUS, dev_addr);
+}
+
+static inline void virtio_notify(unsigned long vq_addr)
+{
+    kvm_hypercall(KVM_S390_VIRTIO_NOTIFY, vq_addr);
+}
+
+static inline void virtio_reset(void *dev)
+{
+    kvm_hypercall(KVM_S390_VIRTIO_RESET, (ulong)dev);
+}
+
+static void early_puts(const char *string)
+{
+#ifdef DEBUG
+    virtio_notify((unsigned long)string);
+#endif
+}
+
+/***********************************************
+ *             Virtio functions                *
+ ***********************************************/
+
+/* Status byte for guest to report progress, and synchronize features. */
+/* We have seen device and processed generic fields (VIRTIO_CONFIG_F_VIRTIO) */
+#define VIRTIO_CONFIG_S_ACKNOWLEDGE     1
+/* We have found a driver for the device. */
+#define VIRTIO_CONFIG_S_DRIVER          2
+/* Driver has used its parts of the config, and is happy */
+#define VIRTIO_CONFIG_S_DRIVER_OK       4
+/* We've given up on this device. */
+#define VIRTIO_CONFIG_S_FAILED          0x80
+
+/*
+ * VirtIO device layout
+ *
+ * All VirtIO devices are described after the top of RAM.
+ * They follow each other, having this following layout:
+ *
+ * -- first device --
+ *
+ * struct virtio_dev_header header
+ * struct virtio_vqconfig vqconfig[header->num_vq]
+ * u8 host_features[header->feature_len]
+ * u8 guest_features[header->feature_len]
+ * u8 config[header->config_len]
+ *
+ * -- second device --
+ * (see above)
+ */
+
+enum virtio_dev_type {
+    VIRTIO_ID_NET = 1,
+    VIRTIO_ID_BLOCK = 2,
+    VIRTIO_ID_CONSOLE = 3,
+    VIRTIO_ID_BALLOON = 5,
+};
+
+struct virtio_dev_header {
+    enum virtio_dev_type type : 8;
+    u8  num_vq;
+    u8  feature_len;
+    u8  config_len;
+    u8  status;
+    u8  vqconfig[];
+} __attribute__((packed));
+
+struct virtio_vqconfig {
+    u64 token;
+    u64 address;
+    u16 num;
+    u8  pad[6];
+} __attribute__((packed));
+
+struct virtio_dev {
+    struct virtio_dev_header *header;
+    struct virtio_vqconfig *vqconfig;
+    char *host_features;
+    char *guest_features;
+    char *config;
+};
+
+#define KVM_S390_VIRTIO_RING_ALIGN	4096
+
+#define VRING_USED_F_NO_NOTIFY  1
+
+/* This marks a buffer as continuing via the next field. */
+#define VRING_DESC_F_NEXT       1
+/* This marks a buffer as write-only (otherwise read-only). */
+#define VRING_DESC_F_WRITE      2
+/* This means the buffer contains a list of buffer descriptors. */
+#define VRING_DESC_F_INDIRECT   4
+
+/* Internal flag to mark follow-up segments as such */
+#define VRING_HIDDEN_IS_CHAIN   256
+
+/* Virtio ring descriptors: 16 bytes.  These can chain together via "next". */
+struct vring_desc {
+    /* Address (guest-physical). */
+    u64 addr;
+    /* Length. */
+    u32 len;
+    /* The flags as indicated above. */
+    u16 flags;
+    /* We chain unused descriptors via this, too */
+    u16 next;
+} __attribute__((packed));
+
+struct vring_avail {
+    u16 flags;
+    u16 idx;
+    u16 ring[];
+} __attribute__((packed));
+
+/* u32 is used here for ids for padding reasons. */
+struct vring_used_elem {
+    /* Index of start of used descriptor chain. */
+    u32 id;
+    /* Total length of the descriptor chain which was used (written to) */
+    u32 len;
+} __attribute__((packed));
+
+struct vring_used {
+    u16 flags;
+    u16 idx;
+    struct vring_used_elem ring[];
+} __attribute__((packed));
+
+struct vring {
+    unsigned int num;
+    int next_idx;
+    struct vring_desc *desc;
+    struct vring_avail *avail;
+    struct vring_used *used;
+};
+
+static inline void vring_init(struct vring *vr, unsigned int num, void *p,
+                              unsigned long align)
+{
+    debug_print_addr("init p", p);
+    vr->num = num;
+    vr->desc = p;
+    vr->avail = p + num*sizeof(struct vring_desc);
+    vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + align-1)
+                & ~(align - 1));
+
+    /* We're running with interrupts off anyways, so don't bother */
+    vr->used->flags = VRING_USED_F_NO_NOTIFY;
+
+    debug_print_addr("init vr", vr);
+}
+
+static inline void vring_notify(struct vring *vr)
+{
+    virtio_notify((unsigned long)vr->desc);
+}
+
+static inline void vring_send_buf(struct vring *vr, void *p, int len,
+                                  int flags)
+{
+    /* For follow-up chains we need to keep the first entry point */
+    if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
+        vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx;
+    }
+
+    vr->desc[vr->next_idx].addr = (ulong)p;
+    vr->desc[vr->next_idx].len = len;
+    vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN;
+    vr->desc[vr->next_idx].next = ++vr->next_idx;
+
+    /* Chains only have a single ID */
+    if (!(flags & VRING_DESC_F_NEXT)) {
+        vr->avail->idx++;
+    }
+
+    vr->used->idx = vr->next_idx;
+}
+
+static inline u64 get_clock(void)
+{
+    u64 r;
+
+    asm volatile("stck %0" : "=Q" (r) : : "cc");
+    return r;
+}
+
+static inline ulong get_second(void)
+{
+    return (get_clock() >> 12) / 1000000;
+}
+
+
+/*
+ * Wait for the host to reply.
+ *
+ * timeout is in seconds if > 0.
+ *
+ * Returns 0 on success, 1 on timeout.
+ */
+static inline int vring_wait_reply(struct vring *vr, int timeout)
+{
+    ulong target_second = get_second() + timeout;
+    int r = 0;
+
+    while (vr->used->idx == vr->next_idx) {
+        vring_notify(vr);
+        if (timeout && (get_second() >= target_second)) {
+            r = 1;
+            break;
+        }
+    }
+
+    vr->next_idx = 0;
+    vr->desc[0].len = 0;
+    vr->desc[0].flags = 0;
+
+    return r;
+}
+
+/***********************************************
+ *               Virtio block                  *
+ ***********************************************/
+
+
+/*
+ * Command types
+ *
+ * Usage is a bit tricky as some bits are used as flags and some are not.
+ *
+ * Rules:
+ *   VIRTIO_BLK_T_OUT may be combined with VIRTIO_BLK_T_SCSI_CMD or
+ *   VIRTIO_BLK_T_BARRIER.  VIRTIO_BLK_T_FLUSH is a command of its own
+ *   and may not be combined with any of the other flags.
+ */
+
+/* These two define direction. */
+#define VIRTIO_BLK_T_IN         0
+#define VIRTIO_BLK_T_OUT        1
+
+/* This bit says it's a scsi command, not an actual read or write. */
+#define VIRTIO_BLK_T_SCSI_CMD   2
+
+/* Cache flush command */
+#define VIRTIO_BLK_T_FLUSH      4
+
+/* Barrier before this op. */
+#define VIRTIO_BLK_T_BARRIER    0x80000000
+
+/* This is the first element of the read scatter-gather list. */
+struct virtio_blk_outhdr {
+        /* VIRTIO_BLK_T* */
+        u32 type;
+        /* io priority. */
+        u32 ioprio;
+        /* Sector (ie. 512 byte offset) */
+        u64 sector;
+};
+
+#define SECTOR_SIZE 512
+
diff --git a/zipl/boot/virtio1.S b/zipl/boot/virtio1.S
new file mode 100644
index 0000000..a96a532
--- /dev/null
+++ b/zipl/boot/virtio1.S
@@ -0,0 +1,30 @@
+/*
+ * First stage boot loader for virtio devices. The compiled output goes
+ * into the pc-bios directory of qemu.
+ *
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+        .globl _start
+_start:
+
+lhi	%r15, 0x7900		/* Set up stack */
+j	virtio_load_stage2	/* And call C */
diff --git a/zipl/boot/virtio2.S b/zipl/boot/virtio2.S
new file mode 100644
index 0000000..50fd3ff
--- /dev/null
+++ b/zipl/boot/virtio2.S
@@ -0,0 +1,81 @@
+/*
+ * S390 stage 2 boot loader for VirtIO
+ *
+ * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+STAGE2_DESC = 0x218
+#define VIRTIO
+#define SCSI
+#define BIT64
+
+#include "common.S"
+# expand the common start code from iplcommon.s
+        stage2_start
+
+# expand blocklist traversing code from iplcommon.s
+        blocklist_traverser
+
+/* Don't provide code for enable/disable device */
+
+_enable_device:
+_disable_device:
+	lhi	%r2, 1
+        br	%r14
+
+/*
+ * For some functions we need to ensure they get called correctly. So
+ * let's provide a common wrapper so we don't have to change the rest
+ * of the code.
+ */
+#define wrap_func(name)							\
+name:;									\
+	j	virtio ## name
+
+wrap_func(_load_direct)
+wrap_func(_extract_length)
+wrap_func(_is_zero_block)
+	
+
+_sclp_read:
+	stm    %r6,%r15,FRAME_R6(%r15)			/* save registers */
+	ahi    %r15,-FRAME_SIZE				/* create stack frame */
+
+	bras   %r14,virtio_sclp_read
+	lhi    %r3,1					/* read: always char */
+
+	lm     %r6,%r15,(FRAME_SIZE+FRAME_R6)(%r15)	/* restore registers */
+	br     %r14
+
+.Llpm:
+	.byte  0x00
+	.align 2
+
+#
+# Panik routine. Loads a disabled wait psw
+#   %r2 : panik code
+#
+_panik:
+	basr   %r1,0
+0:	st     %r2,1f-0b+4(%r1)         # store code in address part of psw
+	lpsw   1f-0b(%r1)
+	.align 8
+1:	.long  0x000a0000,0x00000000
diff --git a/zipl/include/boot.h b/zipl/include/boot.h
index 490cc4a..e298d09 100644
--- a/zipl/include/boot.h
+++ b/zipl/include/boot.h
@@ -184,5 +184,6 @@ int boot_init_eckd_compatible_dump_stage1(
 				struct disk_info* info,
 				int mvdump_switch);
 int boot_get_fba_dump_stage2(void** data, size_t* size, uint64_t mem);
+int boot_get_virtio_stage2(void** data, size_t* size, struct job_data* job);
 
 #endif /* BOOT_H */
diff --git a/zipl/include/disk.h b/zipl/include/disk.h
index 4b39698..1c2b41b 100644
--- a/zipl/include/disk.h
+++ b/zipl/include/disk.h
@@ -44,6 +44,7 @@ typedef union {
 
 /* Disk type identifier */
 typedef enum {
+	disk_type_virtio,
 	disk_type_scsi,
 	disk_type_fba,
 	disk_type_diag,
diff --git a/zipl/src/boot.c b/zipl/src/boot.c
index ddabadb..03ea2f9 100644
--- a/zipl/src/boot.c
+++ b/zipl/src/boot.c
@@ -25,7 +25,7 @@
 #define DATA_SIZE(x)	((size_t) (&_binary_##x##_bin_size))
 #define DATA_ADDR(x)	(&_binary_##x##_bin_start)
 
-#define STAGE2_MAX_SIZE		4096
+static int stage2_max_size = 4096;
 
 
 /* Check sizes of internal objects. Return 0 if everything is correct,
@@ -223,7 +223,7 @@ boot_init_eckd_compatible_stage1(struct boot_eckd_compatible_stage1* stage1,
 	memcpy(stage1, DATA_ADDR(eckd1b),
 	       sizeof(struct boot_eckd_compatible_stage1));
 	/* Fill in blocklist for stage 2 loader */
-	blocks = (STAGE2_MAX_SIZE + info->phy_block_size - 1) /
+	blocks = (stage2_max_size + info->phy_block_size - 1) /
 		 info->phy_block_size;
 	if (blocks > 12) {
 		error_reason("Not enough room for ECKD stage 2 loader "
@@ -475,19 +475,19 @@ boot_get_fba_stage2(void** data, size_t* size, struct job_data* job)
 	void* buffer;
 	int rc;
 
-	buffer = misc_malloc(STAGE2_MAX_SIZE);
+	buffer = misc_malloc(stage2_max_size);
 	if (buffer == NULL)
 		return -1;
 	memcpy(buffer, DATA_ADDR(fba2), DATA_SIZE(fba2));
 	rc = store_stage2_menu(VOID_ADD(buffer, DATA_SIZE(fba2)),
-			       STAGE2_MAX_SIZE - DATA_SIZE(fba2),
+			       stage2_max_size - DATA_SIZE(fba2),
 			       job);
 	if (rc) {
 		free(buffer);
 		return rc;
 	}
 	*data = buffer;
-	*size = STAGE2_MAX_SIZE;
+	*size = stage2_max_size;
 	return 0;
 }
 
@@ -498,19 +498,43 @@ boot_get_eckd_stage2(void** data, size_t* size, struct job_data* job)
 	void* buffer;
 	int rc;
 
-	buffer = misc_malloc(STAGE2_MAX_SIZE);
+	buffer = misc_malloc(stage2_max_size);
 	if (buffer == NULL)
 		return -1;
 	memcpy(buffer, DATA_ADDR(eckd2), DATA_SIZE(eckd2));
 	rc = store_stage2_menu(VOID_ADD(buffer, DATA_SIZE(eckd2)),
-			       STAGE2_MAX_SIZE - DATA_SIZE(eckd2),
+			       stage2_max_size - DATA_SIZE(eckd2),
 			       job);
 	if (rc) {
 		free(buffer);
 		return rc;
 	}
 	*data = buffer;
-	*size = STAGE2_MAX_SIZE;
+	*size = stage2_max_size;
+	return 0;
+}
+
+
+int
+boot_get_virtio_stage2(void** data, size_t* size, struct job_data* job)
+{
+	void* buffer;
+	int rc;
+
+	stage2_max_size = (16 * 512);
+	buffer = misc_malloc(stage2_max_size);
+	if (buffer == NULL)
+		return -1;
+	memcpy(buffer, DATA_ADDR(virtio2), DATA_SIZE(virtio2));
+	rc = store_stage2_menu(VOID_ADD(buffer, DATA_SIZE(virtio2)),
+			       stage2_max_size - DATA_SIZE(virtio2),
+			       job);
+	if (rc) {
+		free(buffer);
+		return rc;
+	}
+	*data = buffer;
+	*size = stage2_max_size;
 	return 0;
 }
 
diff --git a/zipl/src/bootmap.c b/zipl/src/bootmap.c
index 5bb5456..2676648 100644
--- a/zipl/src/bootmap.c
+++ b/zipl/src/bootmap.c
@@ -64,6 +64,7 @@ get_blockptr_size(struct disk_info* info)
 {
 	switch (info->type) {
 	case disk_type_scsi:
+	case disk_type_virtio:
 		return sizeof(struct scsi_blockptr);
 	case disk_type_fba:
 		return sizeof(struct fba_blockptr);
@@ -89,6 +90,7 @@ bootmap_store_blockptr(void* buffer, disk_blockptr_t* ptr,
 	if (ptr != NULL) {
 		switch (info->type) {
 		case disk_type_scsi:
+		case disk_type_virtio:
 			scsi = (struct scsi_blockptr *) buffer;
 			scsi->blockno = ptr->linear.block;
 			scsi->size = ptr->linear.size;
@@ -678,7 +680,8 @@ get_dump_fs_parmline(char* partition, char* parameters, uint64_t mem,
 			   partition);
 		return rc;
 	}
-	if ((info->type != disk_type_scsi) || (info->partnum == 0)) {
+	if (((info->type != disk_type_scsi) && (info->type != disk_type_virtio))
+	 || (info->partnum == 0)) {
 		error_reason("Device '%s' is not a SCSI partition",
 			     partition);
 		disk_free_info(info);
@@ -1019,6 +1022,28 @@ bootmap_create(struct job_data* job, disk_blockptr_t* program_table,
 			return -1;
 		}
 		break;
+	case disk_type_virtio:
+		rc = boot_get_virtio_stage2(&stage2_data, &stage2_size, job);
+		if (rc) {
+			misc_free_temp_dev(device);
+			disk_free_info(info);
+			close(fd);
+			free(filename);
+			return rc;
+		}
+		*stage2_count = disk_write_block_buffer(fd, stage2_data,
+						stage2_size, stage2_list,
+						info);
+		free(stage2_data);
+		if (*stage2_count == 0) {
+			error_text("Could not write to file '%s'", filename);
+			misc_free_temp_dev(device);
+			disk_free_info(info);
+			close(fd);
+			free(filename);
+			return -1;
+		}
+		break;
 	case disk_type_scsi:
 	case disk_type_eckd_compatible:
 	case disk_type_diag:
diff --git a/zipl/src/disk.c b/zipl/src/disk.c
index 6e6a8d1..4e09ddb 100644
--- a/zipl/src/disk.c
+++ b/zipl/src/disk.c
@@ -299,9 +299,8 @@ disk_get_info(const char* device, struct job_target_data* target,
 			goto out_close;
 		data->partnum = stats.st_rdev & DASD_PARTN_MASK;
 		data->device = stats.st_rdev & ~DASD_PARTN_MASK;
-	} else if (strcmp(data->drv_name, "sd") == 0 ||
-		   strcmp(data->drv_name, "virtblk") == 0) {
-		/* Driver name is 'sd' or 'virtblk' */
+	} else if (strcmp(data->drv_name, "sd") == 0) {
+		/* Driver name is 'sd' */
 		if (data->devno != -1) {
 			error_reason("Unsupported device driver '%s' "
 				     "for disk type DASD", data->drv_name);
@@ -310,6 +309,16 @@ disk_get_info(const char* device, struct job_target_data* target,
 		data->type = disk_type_scsi;
 		data->partnum = stats.st_rdev & SCSI_PARTN_MASK;
 		data->device = stats.st_rdev & ~SCSI_PARTN_MASK;
+	} else if (strcmp(data->drv_name, "virtblk") == 0) {
+		/* Driver name is 'virtblk' */
+		if (data->devno != -1) {
+			error_reason("Unsupported device driver '%s' "
+				     "for disk type DASD", data->drv_name);
+			goto out_close;
+		}
+		data->type = disk_type_virtio;
+		data->partnum = stats.st_rdev & SCSI_PARTN_MASK;
+		data->device = stats.st_rdev & ~SCSI_PARTN_MASK;
 	} else {
 		/* Driver name is unknown */
 		error_reason("Unsupported device driver '%s'", data->drv_name);
@@ -493,6 +502,7 @@ disk_blockptr_from_blocknum(disk_blockptr_t* ptr, blocknum_t blocknum,
 			    struct disk_info* info)
 {
 	switch (info->type) {
+	case disk_type_virtio:
 	case disk_type_scsi:
 	case disk_type_fba:
 	case disk_type_diag:
@@ -625,6 +635,8 @@ char *
 disk_get_type_name(disk_type_t type)
 {
 	switch (type) {
+	case disk_type_virtio:
+		return "Virtio disk layout";
 	case disk_type_scsi:
 		return "SCSI disk layout";
 	case disk_type_fba:
@@ -728,6 +740,7 @@ int
 disk_is_zero_block(disk_blockptr_t* block, struct disk_info* info)
 {
 	switch (info->type) {
+	case disk_type_virtio:
 	case disk_type_scsi:
 	case disk_type_fba:
 		return block->linear.block == 0;
@@ -758,11 +771,12 @@ can_merge_blocks(disk_blockptr_t* first, disk_blockptr_t* second,
 	/* Zero blocks can never be merged */
 	if (disk_is_zero_block(first, info) || disk_is_zero_block(second, info))
 		return 0;
-	if (info->type == disk_type_scsi)
+	if ((info->type == disk_type_scsi) || (info->type == disk_type_virtio))
 		max_count = SCSI_MAX_LINK_COUNT;
 	else
 		max_count = DASD_MAX_LINK_COUNT;
 	switch (info->type) {
+	case disk_type_virtio:
 	case disk_type_scsi:
 	case disk_type_fba:
 		/* Check link count limits */
@@ -800,6 +814,7 @@ merge_blocks(disk_blockptr_t* first, disk_blockptr_t* second,
 	     struct disk_info* info)
 {
 	switch (info->type) {
+	case disk_type_virtio:
 	case disk_type_scsi:
 	case disk_type_fba:
 		first->linear.blockct += second->linear.blockct + 1;
diff --git a/zipl/src/install.c b/zipl/src/install.c
index 18abf8d..1186465 100644
--- a/zipl/src/install.c
+++ b/zipl/src/install.c
@@ -62,15 +62,22 @@ get_scsi_layout(unsigned char* bootblock)
  * provides information about the disk. */
 static int
 update_scsi_mbr(void* bootblock, disk_blockptr_t* table,
+		disk_blockptr_t* stage2_list, blocknum_t stage2_count,
 		struct disk_info* info)
 {
 	struct scsi_mbr {
-		uint8_t		magic[4];
-		uint32_t	version_id;
-		uint8_t		reserved[8];
-		uint8_t		program_table_pointer[16];
+		uint8_t			magic[4];
+		uint32_t		version_id;
+		uint8_t			reserved[8];
+		uint8_t			program_table_pointer[16];
+		struct {
+			uint64_t	sector;
+			uint32_t	size;
+			uint32_t	unused;
+		} stage2[16];
 	}  __attribute__ ((packed))* mbr;
 	void* buffer;
+	int i;
 
 	switch (get_scsi_layout(bootblock)) {
 	case scsi_layout_pcbios:
@@ -98,6 +105,16 @@ update_scsi_mbr(void* bootblock, disk_blockptr_t* table,
 	memcpy(&mbr->magic, ZIPL_MAGIC, ZIPL_MAGIC_SIZE);
 	mbr->version_id = DISK_LAYOUT_ID;
 	bootmap_store_blockptr(&mbr->program_table_pointer, table, info);
+
+	/* Virtio can use the bootloader, so let's describe its location in
+	   the MBR */
+	if (info->type == disk_type_virtio) {
+		for (i = 0; i < stage2_count; i++) {
+			mbr->stage2[i].sector = stage2_list[i].linear.block;
+			mbr->stage2[i].size = stage2_list[i].linear.size << 16 |
+					      stage2_list[i].linear.blockct;
+		}
+	}
 	return 0;
 }
 
@@ -107,7 +124,9 @@ update_scsi_mbr(void* bootblock, disk_blockptr_t* table,
  * to the disk block containing the program table. INFO provides
  * information about the disk type. Return 0 on success, non-zero otherwise. */
 static int
-install_scsi(int fd, disk_blockptr_t* program_table, struct disk_info* info)
+install_scsi(int fd, disk_blockptr_t* program_table,
+	     disk_blockptr_t* stage2_list, blocknum_t stage2_count,
+	     struct disk_info* info)
 {
 	unsigned char* bootblock;
 	int rc;
@@ -129,7 +148,8 @@ install_scsi(int fd, disk_blockptr_t* program_table, struct disk_info* info)
 		return rc;
 	}
 	/* Put zIPL data into MBR */
-	rc = update_scsi_mbr(bootblock, program_table, info);
+	rc = update_scsi_mbr(bootblock, program_table, stage2_list,
+			     stage2_count, info);
 	if (rc) {
 		free(bootblock);
 		return -1;
@@ -315,8 +335,10 @@ install_bootloader(const char* device, disk_blockptr_t *program_table,
 	/* Call disk specific install functions */
 	rc = -1;
 	switch (info->type) {
+	case disk_type_virtio:
 	case disk_type_scsi:
-		rc = install_scsi(fd, program_table, info);
+		rc = install_scsi(fd, program_table, stage2_list, stage2_count,
+				  info);
 		break;
 	case disk_type_fba:
 		rc = install_fba(fd, program_table, stage2_list, stage2_count,
@@ -1044,6 +1066,7 @@ install_dump(const char* device, struct job_target_data* target, uint64_t mem)
 		else
 			rc = install_dump_fba(fd, info, mem);
 		break;
+	case disk_type_virtio:
 	case disk_type_scsi:
 		error_reason("%s: Unsupported disk type '%s' (try --dumptofs)",
 			     device, disk_get_type_name(info->type));
-- 
1.6.0.2

