diff --git a/src/arch/arm/Makefile.inc b/src/arch/arm/Makefile.inc index dd4b203e09..3f42b004b8 100644 --- a/src/arch/arm/Makefile.inc +++ b/src/arch/arm/Makefile.inc @@ -55,18 +55,30 @@ bootblock-y += memset.S bootblock-y += memcpy.S bootblock-y += memmove.S -$(objcbfs)/bootblock.debug: $(src)/arch/arm/bootblock.ld $(obj)/ldoptions $$(bootblock-objs) $$(VERSTAGE_LIB) $(obj)/config.h +$(objcbfs)/bootblock.debug: $(src)/arch/arm/bootblock.ld $(obj)/ldoptions $$(bootblock-objs) $$(VERSTAGE_LIB) $$(VB2_LIB) $(obj)/config.h @printf " LINK $(subst $(obj)/,,$(@))\n" ifeq ($(CONFIG_COMPILER_LLVM_CLANG),y) $(LD_bootblock) -nostdlib -m armelf_linux_eabi --gc-sections -static -o $@ -L$(obj) $< -T $(src)/arch/arm/bootblock.ld else # This is based on the assumption that bootblock and verstage are compatible # from the linker's perspective. - $(CC_bootblock) $(CFLAGS_bootblock) -nostdlib -Wl,--gc-sections -static -o $@ -L$(obj) -T $(src)/arch/arm/bootblock.ld -Wl,--start-group $(bootblock-objs) $(VERSTAGE_LIB) -Wl,--end-group + $(CC_bootblock) $(CFLAGS_bootblock) -nostdlib -Wl,--gc-sections -static -o $@ -L$(obj) -T $(src)/arch/arm/bootblock.ld -Wl,--start-group $(bootblock-objs) $(VERSTAGE_LIB) $(VB2_LIB) -Wl,--end-group endif endif +############################################################################### +# verification stage +############################################################################### + +verstage-$(CONFIG_EARLY_CONSOLE) += early_console.c +verstage-y += div0.c +verstage-y += eabi_compat.c +verstage-y += memset.S +verstage-y += memcpy.S +verstage-y += memmove.S +verstage-y += stages.c + ############################################################################### # romstage ############################################################################### diff --git a/src/arch/arm/armv7/Makefile.inc b/src/arch/arm/armv7/Makefile.inc index b73be5d68b..354776a931 100644 --- a/src/arch/arm/armv7/Makefile.inc +++ b/src/arch/arm/armv7/Makefile.inc @@ -44,6 +44,17 @@ bootblock-c-ccopts += $(armv7_flags) bootblock-S-ccopts += $(armv7_asm_flags) endif +################################################################################ +## Verification stage +################################################################################ +verstage-c-ccopts += $(armv7_flags) +verstage-S-ccopts += $(armv7_asm_flags) +verstage-y += cache.c +verstage-y += cpu.S +verstage-y += exception.c +verstage-y += exception_asm.S +verstage-y += mmu.c + ################################################################################ ## ROM stage ################################################################################ diff --git a/src/arch/arm/bootblock.ld b/src/arch/arm/bootblock.ld index 150bf2df6b..c1a6ccf575 100644 --- a/src/arch/arm/bootblock.ld +++ b/src/arch/arm/bootblock.ld @@ -50,6 +50,7 @@ SECTIONS } : to_load = 0xff preram_cbmem_console = CONFIG_CBMEM_CONSOLE_PRERAM_BASE; + verstage_preram_cbmem_console = CONFIG_CBMEM_CONSOLE_PRERAM_BASE; /DISCARD/ : { *(.comment) diff --git a/src/arch/arm/libgcc/Makefile.inc b/src/arch/arm/libgcc/Makefile.inc index 66999b2231..e49731220f 100644 --- a/src/arch/arm/libgcc/Makefile.inc +++ b/src/arch/arm/libgcc/Makefile.inc @@ -25,6 +25,10 @@ ifeq ($(CONFIG_ARCH_BOOTBLOCK_ARM),y) bootblock-y += $(libgcc_files) endif +ifeq ($(CONFIG_ARCH_VERSTAGE_ARM),y) +verstage-y += $(libgcc_files) +endif + ifeq ($(CONFIG_ARCH_ROMSTAGE_ARM),y) romstage-y += $(libgcc_files) endif diff --git a/src/console/Makefile.inc b/src/console/Makefile.inc index dd2527fead..db6604339c 100644 --- a/src/console/Makefile.inc +++ b/src/console/Makefile.inc @@ -9,6 +9,10 @@ smm-y += printk.c smm-y += vtxprintf.c smm-$(CONFIG_SMM_TSEG) += die.c +verstage-$(CONFIG_EARLY_CONSOLE) += vtxprintf.c +verstage-y += console.c +verstage-y += die.c + romstage-$(CONFIG_EARLY_CONSOLE) += vtxprintf.c romstage-y += console.c romstage-y += post.c diff --git a/src/console/console.c b/src/console/console.c index de62546ea7..069198d722 100644 --- a/src/console/console.c +++ b/src/console/console.c @@ -126,6 +126,8 @@ void console_init(void) COREBOOT_EXTRA_VERSION #if defined(__BOOT_BLOCK__) " bootblock " +#elif defined(__VER_STAGE__) + " verstage " #else " romstage " #endif diff --git a/src/include/reset.h b/src/include/reset.h index 9f117dbda1..9430ffef56 100644 --- a/src/include/reset.h +++ b/src/include/reset.h @@ -7,5 +7,6 @@ void hard_reset(void); #define hard_reset() do {} while(0) #endif void soft_reset(void); +void cpu_reset(void); #endif diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index f9ce39e3ee..6d69bf48fe 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -32,6 +32,12 @@ endif romstage-y += memchr.c romstage-y += memcmp.c rmodules-y += memcmp.c + +verstage-y += delay.c +verstage-y += cbfs.c +verstage-y += memcmp.c +verstage-$(CONFIG_CONSOLE_CBMEM) += cbmem_console.c + romstage-y += delay.c romstage-y += cbfs.c romstage-$(CONFIG_COMMON_CBFS_SPI_WRAPPER) += cbfs_spi.c diff --git a/src/lib/cbfs.c b/src/lib/cbfs.c index ec00d62fdb..ef6d0d86ee 100644 --- a/src/lib/cbfs.c +++ b/src/lib/cbfs.c @@ -27,7 +27,7 @@ # define CBFS_MINI_BUILD #elif defined(__SMM__) # define CBFS_MINI_BUILD -#elif defined(__BOOT_BLOCK__) +#elif defined(__BOOT_BLOCK__) || defined(__VER_STAGE__) /* No LZMA in boot block. */ #else # define CBFS_CORE_WITH_LZMA diff --git a/src/mainboard/google/nyan_blaze/Makefile.inc b/src/mainboard/google/nyan_blaze/Makefile.inc index 3c2b5da30f..ced15d34b5 100644 --- a/src/mainboard/google/nyan_blaze/Makefile.inc +++ b/src/mainboard/google/nyan_blaze/Makefile.inc @@ -32,6 +32,8 @@ bootblock-y += bootblock.c bootblock-y += pmic.c bootblock-y += reset.c +verstage-y += reset.c + romstage-y += reset.c romstage-y += romstage.c romstage-y += sdram_configs.c diff --git a/src/mainboard/google/nyan_blaze/romstage.c b/src/mainboard/google/nyan_blaze/romstage.c index 4f99e5435a..b28fbebaa7 100644 --- a/src/mainboard/google/nyan_blaze/romstage.c +++ b/src/mainboard/google/nyan_blaze/romstage.c @@ -228,7 +228,11 @@ static void __attribute__((noinline)) romstage(void) cbmemc_reinit(); #endif +#if CONFIG_VBOOT2_VERIFY_FIRMWARE + // vboot_create_handoff((void *)CONFIG_VBOOT_WORK_BUFFER_ADDRESS); +#else vboot_verify_firmware(romstage_handoff_find_or_add()); +#endif timestamp_add(TS_START_COPYRAM, timestamp_get()); void *entry = cbfs_load_stage(CBFS_DEFAULT_MEDIA, diff --git a/src/soc/nvidia/tegra124/Kconfig b/src/soc/nvidia/tegra124/Kconfig index 7fc04757e5..bedf420415 100644 --- a/src/soc/nvidia/tegra124/Kconfig +++ b/src/soc/nvidia/tegra124/Kconfig @@ -48,10 +48,12 @@ config BOOTBLOCK_ROM_OFFSET config CBFS_HEADER_ROM_OFFSET hex "offset of master CBFS header in ROM" + default 0x1e000 if VBOOT2_VERIFY_FIRMWARE default 0x18000 config CBFS_ROM_OFFSET hex "offset of CBFS data in ROM" + default 0x1e080 if VBOOT2_VERIFY_FIRMWARE default 0x18080 config SYS_SDRAM_BASE @@ -64,6 +66,7 @@ config BOOTBLOCK_BASE config ROMSTAGE_BASE hex + default 0x4002d000 if VBOOT2_VERIFY_FIRMWARE default 0x4002c000 config RAMSTAGE_BASE @@ -96,6 +99,14 @@ config CBFS_CACHE_SIZE hex "size of CBFS cache data" default 0x00016000 +config VBOOT_WORK_BUFFER_ADDRESS + hex "memory address of vboot work buffer" + default 0x40018000 + +config VBOOT_WORK_BUFFER_SIZE + hex "size of vboot work buffer" + default 0x00004000 + config CBMEM_CONSOLE_PRERAM_BASE hex "memory address of the CBMEM console buffer" default 0x40004020 diff --git a/src/soc/nvidia/tegra124/Makefile.inc b/src/soc/nvidia/tegra124/Makefile.inc index 80f7894500..d30ec0849c 100644 --- a/src/soc/nvidia/tegra124/Makefile.inc +++ b/src/soc/nvidia/tegra124/Makefile.inc @@ -23,6 +23,14 @@ bootblock-$(CONFIG_CONSOLE_SERIAL_UART) += uart.c endif verstage-y += verstage.c +verstage-y += cbfs.c +verstage-y += dma.c +verstage-y += monotonic_timer.c +verstage-y += spi.c +verstage-y += timer.c +verstage-$(CONFIG_CONSOLE_SERIAL_UART) += uart.c +verstage-y += ../tegra/gpio.c +verstage-y += ../tegra/pinmux.c romstage-y += cbfs.c romstage-y += cbmem.c diff --git a/src/soc/nvidia/tegra124/verstage.c b/src/soc/nvidia/tegra124/verstage.c index 234a89d0b2..d85fc5c8cf 100644 --- a/src/soc/nvidia/tegra124/verstage.c +++ b/src/soc/nvidia/tegra124/verstage.c @@ -1,9 +1,15 @@ #include "verstage.h" +#include /** * Stage entry point */ void vboot_main(void) { - for(;;); + /* Stub to force arm_init_caches to the top, before any stack/memory + * accesses */ + asm volatile ("bl arm_init_caches" + ::: "r0","r1","r2","r3","r4","r5","ip"); + + select_firmware(); } diff --git a/src/vendorcode/google/chromeos/Makefile.inc b/src/vendorcode/google/chromeos/Makefile.inc index 3f70453c0e..52db5ed931 100644 --- a/src/vendorcode/google/chromeos/Makefile.inc +++ b/src/vendorcode/google/chromeos/Makefile.inc @@ -106,7 +106,33 @@ $(VB_LIB): endif ifeq ($(CONFIG_VBOOT2_VERIFY_FIRMWARE),y) +VB_SOURCE := vboot_reference VERSTAGE_LIB = $(obj)/vendorcode/google/chromeos/verstage.a + +INCLUDES += -I$(VB_SOURCE)/firmware/2lib/include +INCLUDES += -I$(VB_SOURCE)/firmware/include +verstage-y += vboot_main.c fmap.c chromeos.c + +VB_FIRMWARE_ARCH := $(ARCHDIR-$(ARCH-VERSTAGE-y)) +VB2_LIB = $(obj)/external/vboot_reference/vboot_fw2.a +VBOOT_CFLAGS += $(patsubst -I%,-I$(top)/%,$(filter-out -include $(src)/include/kconfig.h, $(CFLAGS_verstage))) +VBOOT_CFLAGS += $(verstage-c-ccopts) +VBOOT_CFLAGS += -include $(top)/src/include/kconfig.h -Wno-missing-prototypes +VBOOT_CFLAGS += -DVBOOT_DEBUG + +$(VB2_LIB): $(obj)/config.h + @printf " MAKE $(subst $(obj)/,,$(@))\n" + $(Q)FIRMWARE_ARCH=$(VB_FIRMWARE_ARCH) \ + CC="$(CC_verstage)" \ + CFLAGS="$(VBOOT_CFLAGS)" VBOOT2="y" \ + make -C $(VB_SOURCE) \ + BUILD=$(top)/$(dir $(VB2_LIB)) \ + V=$(V) \ + fwlib2 + mv $@ $@.tmp + @printf " OBJCOPY $(subst $(obj)/,,$(@))\n" + $(OBJCOPY_verstage) --prefix-symbols=verstage_ $@.tmp $@ + $(VERSTAGE_LIB): $$(verstage-objs) @printf " AR $(subst $(obj)/,,$(@))\n" $(AR_verstage) rc $@.tmp $(verstage-objs) diff --git a/src/vendorcode/google/chromeos/chromeos.c b/src/vendorcode/google/chromeos/chromeos.c index d28b05c6cb..d45881a87b 100644 --- a/src/vendorcode/google/chromeos/chromeos.c +++ b/src/vendorcode/google/chromeos/chromeos.c @@ -118,7 +118,7 @@ int __attribute((weak)) vboot_get_sw_write_protect(void) } #endif -#if CONFIG_VBOOT_VERIFY_FIRMWARE +#if CONFIG_VBOOT_VERIFY_FIRMWARE || CONFIG_VBOOT2_VERIFY_FIRMWARE void *vboot_get_region(uintptr_t offset_addr, size_t size, void *dest) { if (IS_ENABLED(CONFIG_SPI_FLASH_MEMORY_MAPPED)) { @@ -145,7 +145,13 @@ void *vboot_get_region(uintptr_t offset_addr, size_t size, void *dest) return cache; } } +#endif +#if CONFIG_VBOOT2_VERIFY_FIRMWARE +void *vboot_get_payload(int *len) { return NULL; } +#endif + +#if CONFIG_VBOOT_VERIFY_FIRMWARE void *vboot_get_payload(int *len) { struct vboot_handoff *vboot_handoff; diff --git a/src/vendorcode/google/chromeos/chromeos.h b/src/vendorcode/google/chromeos/chromeos.h index 8659aa1a70..a439286c05 100644 --- a/src/vendorcode/google/chromeos/chromeos.h +++ b/src/vendorcode/google/chromeos/chromeos.h @@ -63,7 +63,7 @@ struct romstage_handoff; /* TODO(shawnn): Remove these CONFIGs and define default weak functions * that can be overridden in the platform / MB code. */ -#if CONFIG_VBOOT_VERIFY_FIRMWARE +#if CONFIG_VBOOT_VERIFY_FIRMWARE || CONFIG_VBOOT2_VERIFY_FIRMWARE /* * This is a dual purpose routine. If dest is non-NULL the region at * offset_addr will be read into the area pointed to by dest. If dest @@ -82,7 +82,8 @@ static inline int vboot_get_handoff_info(void **addr, uint32_t *size) { return -1; } -#endif +#endif /* CONFIG_VBOOT_VERIFY_FIRMWARE || CONFIG_VBOOT2_VERIFY_FIRMWARE */ + int vboot_get_sw_write_protect(void); #include "gnvs.h" @@ -100,4 +101,8 @@ static inline void chromeos_ram_oops_init(chromeos_acpi_t *chromeos) {} static inline void chromeos_reserve_ram_oops(struct device *dev, int idx) {} #endif /* CONFIG_CHROMEOS_RAMOOPS */ +#if CONFIG_VBOOT2_VERIFY_FIRMWARE +void select_firmware(void); +#endif + #endif diff --git a/src/vendorcode/google/chromeos/vboot_main.c b/src/vendorcode/google/chromeos/vboot_main.c new file mode 100644 index 0000000000..9779d35eea --- /dev/null +++ b/src/vendorcode/google/chromeos/vboot_main.c @@ -0,0 +1,311 @@ +#include <2api.h> +#include <2struct.h> +#include +#include +#include +#include +#include +#include + +#include "chromeos.h" +#include "fmap.h" + +#define VBDEBUG(format, args...) \ + printk(BIOS_INFO, "%s():%d: " format, __func__, __LINE__, ## args) +#define TODO_BLOCK_SIZE 8192 +#define MAX_PARSED_FW_COMPONENTS 5 +#define ROMSTAGE_INDEX 2 + +struct component_entry { + uint32_t offset; + uint32_t size; +} __attribute__((packed)); + +struct components { + uint32_t num_components; + struct component_entry entries[MAX_PARSED_FW_COMPONENTS]; +} __attribute__((packed)); + +struct vboot_region { + uintptr_t offset_addr; + int32_t size; +}; + +/* exports */ + +void vb2ex_printf(const char *func, const char *fmt, ...) +{ + va_list args; + + printk(BIOS_INFO, "VB2:%s() ", func); + va_start(args, fmt); + printk(BIOS_INFO, fmt, args); + va_end(args); + + return; +} + +int vb2ex_tpm_clear_owner(struct vb2_context *ctx) +{ + VBDEBUG("Clearing owner\n"); + return VB2_ERROR_UNKNOWN; +} + +int vb2ex_read_resource(struct vb2_context *ctx, + enum vb2_resource_index index, + uint32_t offset, + void *buf, + uint32_t size) +{ + VBDEBUG("Reading resource\n"); + return VB2_ERROR_UNKNOWN; +} + +/* locals */ + +static void locate_region(const char *name, struct vboot_region *region) +{ + region->size = find_fmap_entry(name, (void **)®ion->offset_addr); + VBDEBUG("Located %s @%x\n", name, region->offset_addr); +} + +static int is_slot_a(struct vb2_context *ctx) +{ + return !(ctx->flags & VB2_CONTEXT_FW_SLOT_B); +} + +static int in_ro(void) +{ + /* TODO: Implement */ + return 1; +} + +static void reboot(void) +{ + cpu_reset(); +} + +static void recovery(void) +{ + void *entry; + + if (!in_ro()) + reboot(); + + entry = cbfs_load_stage(CBFS_DEFAULT_MEDIA, "fallback/romstage"); + if (entry != (void *)-1) + stage_exit(entry); + + for(;;); +} + +static int hash_body(struct vb2_context *ctx, struct vboot_region *fw_main) +{ + uint32_t expected_size; + uint8_t block[TODO_BLOCK_SIZE]; + size_t block_size = sizeof(block); + uintptr_t offset; + int rv; + + expected_size = fw_main->size; + offset= fw_main->offset_addr; + + /* Start the body hash */ + rv = vb2api_init_hash(ctx, VB2_HASH_TAG_FW_BODY, &expected_size); + if (rv) { + return rv; + } + + /* Extend over the body */ + while (expected_size) { + void *b; + if (block_size > expected_size) + block_size = expected_size; + + b = vboot_get_region(offset, block_size, block); + if (b == NULL) + return VB2_ERROR_UNKNOWN; + rv = vb2api_extend_hash(ctx, b, block_size); + if (rv) + return rv; + + expected_size -= block_size; + offset+= block_size; + } + + /* Check the result */ + rv = vb2api_check_hash(ctx); + if (rv) { + return rv; + } + + return VB2_SUCCESS; +} + +static int locate_fw_components(struct vb2_context *ctx, + struct vboot_region *fw_main, + struct components *fw_info) +{ + if (is_slot_a(ctx)) + locate_region("FW_MAIN_A", fw_main); + else + locate_region("FW_MAIN_B", fw_main); + if (fw_main->size < 0) + return 1; + + if (vboot_get_region(fw_main->offset_addr, + sizeof(*fw_info), fw_info) == NULL) + return 1; + return 0; +} + +static struct cbfs_stage *load_stage(struct vb2_context *ctx, + int stage_index, + struct vboot_region *fw_main, + struct components *fw_info) +{ + struct cbfs_stage *stage; + uint32_t fc_addr; + uint32_t fc_size; + + /* Check for invalid address. */ + fc_addr = fw_main->offset_addr + fw_info->entries[stage_index].offset; + fc_size = fw_info->entries[stage_index].size; + if (fc_addr == 0 || fc_size == 0) { + VBDEBUG("romstage address invalid.\n"); + return NULL; + } + + /* Loading to cbfs cache. This stage data must be retained until it's + * decompressed. */ + stage = vboot_get_region(fc_addr, fc_size, NULL); + + if (stage == NULL) { + VBDEBUG("Unable to load a stage.\n"); + return NULL; + } + + return stage; +} + +static void enter_stage(struct cbfs_stage *stage) +{ + /* Stages rely the below clearing so that the bss is initialized. */ + memset((void *) (uintptr_t)stage->load, 0, stage->memlen); + + if (cbfs_decompress(stage->compression, + (unsigned char *)stage + sizeof(*stage), + (void *) (uintptr_t) stage->load, + stage->len)) + return; + + VBDEBUG("Jumping to entry @%llx.\n", stage->entry); + stage_exit((void *)(uintptr_t)stage->entry); +} + +/** + * Save non-volatile and/or secure data if needed. + */ +static void save_if_needed(struct vb2_context *ctx) +{ + if (ctx->flags & VB2_CONTEXT_NVDATA_CHANGED) { + VBDEBUG("Saving nvdata\n"); + //save_vbnv(ctx->nvdata); + ctx->flags &= ~VB2_CONTEXT_NVDATA_CHANGED; + } + if (ctx->flags & VB2_CONTEXT_SECDATA_CHANGED) { + VBDEBUG("Saving secdata\n"); + //antirollback_write_space_firmware(ctx); + ctx->flags &= ~VB2_CONTEXT_SECDATA_CHANGED; + } +} + +void __attribute__((noinline)) select_firmware(void) +{ + struct vb2_context ctx; + uint8_t *workbuf = (uint8_t *)CONFIG_VBOOT_WORK_BUFFER_ADDRESS; + struct vboot_region fw_main; + struct components fw_info; + struct cbfs_stage *stage; + int rv; + + console_init(); + + /* Set up context */ + memset(&ctx, 0, sizeof(ctx)); + ctx.workbuf = workbuf; + ctx.workbuf_size = CONFIG_VBOOT_WORK_BUFFER_SIZE; + memset(ctx.workbuf, 0, ctx.workbuf_size); + + /* Read nvdata from a non-volatile storage */ + //read_vbnv(ctx.nvdata); + + /* Read secdata from TPM. Initialize TPM if secdata not found. We don't + * check the return value here because vb2api_fw_phase1 will catch + * invalid secdata and tell us what to do (=reboot). */ + //antirollback_read_space_firmware(&ctx); + + //if (get_developer_mode_switch()) + // ctx.flags |= VB2_CONTEXT_FORCE_DEVELOPER_MODE; + //if (get_recovery_mode_switch()) { + // clear_recovery_mode_switch(); + // ctx.flags |= VB2_CONTEXT_FORCE_RECOVERY_MODE; + //} + + /* Do early init */ + VBDEBUG("Phase 1\n"); + rv = vb2api_fw_phase1(&ctx); + if (rv) { + VBDEBUG("Recovery requested (%x)\n", rv); + /* If we need recovery mode, leave firmware selection now */ + save_if_needed(&ctx); + recovery(); + } + + /* Determine which firmware slot to boot */ + VBDEBUG("Phase 2\n"); + rv = vb2api_fw_phase2(&ctx); + if (rv) { + VBDEBUG("Reboot requested (%x)\n", rv); + save_if_needed(&ctx); + reboot(); + } + + /* Try that slot */ + VBDEBUG("Phase 3\n"); + rv = vb2api_fw_phase3(&ctx); + if (rv) { + VBDEBUG("Reboot requested (%x)\n", rv); + save_if_needed(&ctx); + reboot(); + } + + VBDEBUG("Phase 4\n"); + rv = locate_fw_components(&ctx, &fw_main, &fw_info); + if (rv) { + VBDEBUG("Failed to locate firmware components\n"); + reboot(); + } + rv = hash_body(&ctx, &fw_main); + stage = load_stage(&ctx, ROMSTAGE_INDEX, &fw_main, &fw_info); + if (stage == NULL) { + VBDEBUG("Failed to load stage\n"); + reboot(); + } + save_if_needed(&ctx); + if (rv) { + VBDEBUG("Reboot requested (%x)\n", rv); + reboot(); + } + + /* TODO: Do we need to lock secdata? */ + VBDEBUG("Locking TPM\n"); + + /* Load next stage and jump to it */ + VBDEBUG("Jumping to rw-romstage @%llx\n", stage->entry); + enter_stage(stage); + + /* Shouldn't reach here */ + VBDEBUG("Halting\n"); + for(;;); +}