Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 968de4f0 authored by Eric W. Biederman's avatar Eric W. Biederman Committed by Andi Kleen
Browse files

[PATCH] i386: Relocatable kernel support



This patch modifies the i386 kernel so that if CONFIG_RELOCATABLE is
selected it will be able to be loaded at any 4K aligned address below
1G.  The technique used is to compile the decompressor with -fPIC and
modify it so the decompressor is fully relocatable.  For the main
kernel relocations are generated.  Resulting in a kernel that is relocatable
with no runtime overhead and no need to modify the source code.

A reserved 32bit word in the parameters has been assigned
to serve as a stack so we figure out where are running.

Signed-off-by: default avatarEric W. Biederman <ebiederm@xmission.com>
Signed-off-by: default avatarVivek Goyal <vgoyal@in.ibm.com>
Signed-off-by: default avatarAndi Kleen <ak@suse.de>
parent fd593d12
Loading
Loading
Loading
Loading
+12 −0
Original line number Original line Diff line number Diff line
@@ -773,6 +773,18 @@ config CRASH_DUMP
          PHYSICAL_START.
          PHYSICAL_START.
	  For more details see Documentation/kdump/kdump.txt
	  For more details see Documentation/kdump/kdump.txt


config RELOCATABLE
	bool "Build a relocatable kernel"
	help
	  This build a kernel image that retains relocation information
          so it can be loaded someplace besides the default 1MB.
	  The relocations tend to the kernel binary about 10% larger,
          but are discarded at runtime.

	  One use is for the kexec on panic case where the recovery kernel
          must live at a different physical address than the primary
          kernel.

config PHYSICAL_START
config PHYSICAL_START
	hex "Physical address where the kernel is loaded" if (EMBEDDED || CRASH_DUMP)
	hex "Physical address where the kernel is loaded" if (EMBEDDED || CRASH_DUMP)


+3 −1
Original line number Original line Diff line number Diff line
@@ -26,7 +26,9 @@ endif


LDFLAGS		:= -m elf_i386
LDFLAGS		:= -m elf_i386
OBJCOPYFLAGS	:= -O binary -R .note -R .comment -S
OBJCOPYFLAGS	:= -O binary -R .note -R .comment -S
LDFLAGS_vmlinux :=
ifdef CONFIG_RELOCATABLE
LDFLAGS_vmlinux := --emit-relocs
endif
CHECKFLAGS	+= -D__i386__
CHECKFLAGS	+= -D__i386__


CFLAGS += -pipe -msoft-float
CFLAGS += -pipe -msoft-float
+24 −4
Original line number Original line Diff line number Diff line
@@ -4,22 +4,42 @@
# create a compressed vmlinux image from the original vmlinux
# create a compressed vmlinux image from the original vmlinux
#
#


targets		:= vmlinux vmlinux.bin vmlinux.bin.gz head.o misc.o piggy.o
targets		:= vmlinux vmlinux.bin vmlinux.bin.gz head.o misc.o piggy.o \
			vmlinux.bin.all vmlinux.relocs
EXTRA_AFLAGS	:= -traditional
EXTRA_AFLAGS	:= -traditional


LDFLAGS_vmlinux := -Ttext $(IMAGE_OFFSET) -e startup_32
LDFLAGS_vmlinux := -T
CFLAGS_misc.o += -fPIC
hostprogs-y	:= relocs


$(obj)/vmlinux: $(obj)/head.o $(obj)/misc.o $(obj)/piggy.o FORCE
$(obj)/vmlinux: $(src)/vmlinux.lds $(obj)/head.o $(obj)/misc.o $(obj)/piggy.o FORCE
	$(call if_changed,ld)
	$(call if_changed,ld)
	@:
	@:


$(obj)/vmlinux.bin: vmlinux FORCE
$(obj)/vmlinux.bin: vmlinux FORCE
	$(call if_changed,objcopy)
	$(call if_changed,objcopy)


quiet_cmd_relocs = RELOCS  $@
      cmd_relocs = $(obj)/relocs $< > $@
$(obj)/vmlinux.relocs: vmlinux $(obj)/relocs FORCE
	$(call if_changed,relocs)

vmlinux.bin.all-y := $(obj)/vmlinux.bin
vmlinux.bin.all-$(CONFIG_RELOCATABLE) += $(obj)/vmlinux.relocs
quiet_cmd_relocbin = BUILD   $@
      cmd_relocbin = cat $(filter-out FORCE,$^) > $@
$(obj)/vmlinux.bin.all: $(vmlinux.bin.all-y) FORCE
	$(call if_changed,relocbin)

ifdef CONFIG_RELOCATABLE
$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin.all FORCE
	$(call if_changed,gzip)
else
$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE
$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE
	$(call if_changed,gzip)
	$(call if_changed,gzip)
endif


LDFLAGS_piggy.o := -r --format binary --oformat elf32-i386 -T
LDFLAGS_piggy.o := -r --format binary --oformat elf32-i386 -T


$(obj)/piggy.o: $(obj)/vmlinux.scr $(obj)/vmlinux.bin.gz FORCE
$(obj)/piggy.o: $(src)/vmlinux.scr $(obj)/vmlinux.bin.gz FORCE
	$(call if_changed,ld)
	$(call if_changed,ld)
+117 −67
Original line number Original line Diff line number Diff line
@@ -25,7 +25,9 @@


#include <linux/linkage.h>
#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/segment.h>
#include <asm/page.h>


.section ".text.head"
	.globl startup_32
	.globl startup_32


startup_32:
startup_32:
@@ -36,93 +38,141 @@ startup_32:
	movl %eax,%es
	movl %eax,%es
	movl %eax,%fs
	movl %eax,%fs
	movl %eax,%gs
	movl %eax,%gs
	movl %eax,%ss


	lss stack_start,%esp
/* Calculate the delta between where we were compiled to run
	xorl %eax,%eax
 * at and where we were actually loaded at.  This can only be done
1:	incl %eax		# check that A20 really IS enabled
 * with a short local call on x86.  Nothing  else will tell us what
	movl %eax,0x000000	# loop forever if it isn't
 * address we are running at.  The reserved chunk of the real-mode
	cmpl %eax,0x100000
 * data at 0x34-0x3f are used as the stack for this calculation.
	je 1b
 * Only 4 bytes are needed.
 */
	leal 0x40(%esi), %esp
	call 1f
1:	popl %ebp
	subl $1b, %ebp

/* Compute the delta between where we were compiled to run at
 * and where the code will actually run at.
 */
	/* Start with the delta to where the kernel will run at.  If we are
	 * a relocatable kernel this is the delta to our load address otherwise
	 * this is the delta to CONFIG_PHYSICAL start.
	 */
#ifdef CONFIG_RELOCATABLE
	movl %ebp, %ebx
#else
	movl $(CONFIG_PHYSICAL_START - startup_32), %ebx
#endif

	/* Replace the compressed data size with the uncompressed size */
	subl input_len(%ebp), %ebx
	movl output_len(%ebp), %eax
	addl %eax, %ebx
	/* Add 8 bytes for every 32K input block */
	shrl $12, %eax
	addl %eax, %ebx
	/* Add 32K + 18 bytes of extra slack */
	addl $(32768 + 18), %ebx
	/* Align on a 4K boundary */
	addl $4095, %ebx
	andl $~4095, %ebx

/* Copy the compressed kernel to the end of our buffer
 * where decompression in place becomes safe.
 */
	pushl %esi
	leal _end(%ebp), %esi
	leal _end(%ebx), %edi
	movl $(_end - startup_32), %ecx
	std
	rep
	movsb
	cld
	popl %esi

/* Compute the kernel start address.
 */
#ifdef CONFIG_RELOCATABLE
	leal	startup_32(%ebp), %ebp
#else
	movl	$CONFIG_PHYSICAL_START, %ebp
#endif


/*
/*
 * Initialize eflags.  Some BIOS's leave bits like NT set.  This would
 * Jump to the relocated address.
 * confuse the debugger if this code is traced.
 * XXX - best to initialize before switching to protected mode.
 */
 */
	pushl $0
	leal relocated(%ebx), %eax
	popfl
	jmp *%eax
.section ".text"
relocated:

/*
/*
 * Clear BSS
 * Clear BSS
 */
 */
	xorl %eax,%eax
	xorl %eax,%eax
	movl $_edata,%edi
	leal _edata(%ebx),%edi
	movl $_end,%ecx
	leal _end(%ebx), %ecx
	subl %edi,%ecx
	subl %edi,%ecx
	cld
	cld
	rep
	rep
	stosb
	stosb

/*
 * Setup the stack for the decompressor
 */
	leal stack_end(%ebx), %esp

/*
/*
 * Do the decompression, and jump to the new kernel..
 * Do the decompression, and jump to the new kernel..
 */
 */
	subl $16,%esp	# place for structure on the stack
	movl output_len(%ebx), %eax
	movl %esp,%eax
	pushl %eax
	pushl %ebp	# output address
	movl input_len(%ebx), %eax
	pushl %eax	# input_len
	leal input_data(%ebx), %eax
	pushl %eax	# input_data
	leal _end(%ebx), %eax
	pushl %eax	# end of the image as third argument
	pushl %esi	# real mode pointer as second arg
	pushl %esi	# real mode pointer as second arg
	pushl %eax	# address of structure as first arg
	call decompress_kernel
	call decompress_kernel
	orl  %eax,%eax 
	addl $20, %esp
	jnz  3f
	popl %ecx
	popl %esi	# discard address

	popl %esi	# real mode pointer
#if CONFIG_RELOCATABLE
	xorl %ebx,%ebx
/* Find the address of the relocations.
	ljmp $(__BOOT_CS), $CONFIG_PHYSICAL_START
 */
	movl %ebp, %edi
	addl %ecx, %edi

/* Calculate the delta between where vmlinux was compiled to run
 * and where it was actually loaded.
 */
	movl %ebp, %ebx
	subl $CONFIG_PHYSICAL_START, %ebx


/*
/*
 * We come here, if we were loaded high.
 * Process relocations.
 * We need to move the move-in-place routine down to 0x1000
 */
 * and then start it with the buffer addresses in registers,

 * which we got from the stack.
1:	subl $4, %edi
 */
	movl 0(%edi), %ecx
3:
	testl %ecx, %ecx
	movl $move_routine_start,%esi
	jz 2f
	movl $0x1000,%edi
	addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)
	movl $move_routine_end,%ecx
	jmp 1b
	subl %esi,%ecx
2:
	addl $3,%ecx
#endif
	shrl $2,%ecx
	cld
	rep
	movsl

	popl %esi	# discard the address
	popl %ebx	# real mode pointer
	popl %esi	# low_buffer_start
	popl %ecx	# lcount
	popl %edx	# high_buffer_start
	popl %eax	# hcount
	movl $CONFIG_PHYSICAL_START,%edi
	cli		# make sure we don't get interrupted
	ljmp $(__BOOT_CS), $0x1000 # and jump to the move routine


/*
/*
 * Routine (template) for moving the decompressed kernel in place,
 * Jump to the decompressed kernel.
 * if we were high loaded. This _must_ PIC-code !
 */
 */
move_routine_start:
	movl %ecx,%ebp
	shrl $2,%ecx
	rep
	movsl
	movl %ebp,%ecx
	andl $3,%ecx
	rep
	movsb
	movl %edx,%esi
	movl %eax,%ecx	# NOTE: rep movsb won't move if %ecx == 0
	addl $3,%ecx
	shrl $2,%ecx
	rep
	movsl
	movl %ebx,%esi	# Restore setup pointer
	xorl %ebx,%ebx
	xorl %ebx,%ebx
	ljmp $(__BOOT_CS), $CONFIG_PHYSICAL_START
	jmp *%ebp
move_routine_end:

.bss
.balign 4
stack:
	.fill 4096, 1, 0
stack_end:
+133 −128
Original line number Original line Diff line number Diff line
@@ -13,6 +13,88 @@
#include <linux/vmalloc.h>
#include <linux/vmalloc.h>
#include <linux/screen_info.h>
#include <linux/screen_info.h>
#include <asm/io.h>
#include <asm/io.h>
#include <asm/page.h>

/* WARNING!!
 * This code is compiled with -fPIC and it is relocated dynamically
 * at run time, but no relocation processing is performed.
 * This means that it is not safe to place pointers in static structures.
 */

/*
 * Getting to provable safe in place decompression is hard.
 * Worst case behaviours need to be analized.
 * Background information:
 *
 * The file layout is:
 *    magic[2]
 *    method[1]
 *    flags[1]
 *    timestamp[4]
 *    extraflags[1]
 *    os[1]
 *    compressed data blocks[N]
 *    crc[4] orig_len[4]
 *
 * resulting in 18 bytes of non compressed data overhead.
 *
 * Files divided into blocks
 * 1 bit (last block flag)
 * 2 bits (block type)
 *
 * 1 block occurs every 32K -1 bytes or when there 50% compression has been achieved.
 * The smallest block type encoding is always used.
 *
 * stored:
 *    32 bits length in bytes.
 *
 * fixed:
 *    magic fixed tree.
 *    symbols.
 *
 * dynamic:
 *    dynamic tree encoding.
 *    symbols.
 *
 *
 * The buffer for decompression in place is the length of the
 * uncompressed data, plus a small amount extra to keep the algorithm safe.
 * The compressed data is placed at the end of the buffer.  The output
 * pointer is placed at the start of the buffer and the input pointer
 * is placed where the compressed data starts.  Problems will occur
 * when the output pointer overruns the input pointer.
 *
 * The output pointer can only overrun the input pointer if the input
 * pointer is moving faster than the output pointer.  A condition only
 * triggered by data whose compressed form is larger than the uncompressed
 * form.
 *
 * The worst case at the block level is a growth of the compressed data
 * of 5 bytes per 32767 bytes.
 *
 * The worst case internal to a compressed block is very hard to figure.
 * The worst case can at least be boundined by having one bit that represents
 * 32764 bytes and then all of the rest of the bytes representing the very
 * very last byte.
 *
 * All of which is enough to compute an amount of extra data that is required
 * to be safe.  To avoid problems at the block level allocating 5 extra bytes
 * per 32767 bytes of data is sufficient.  To avoind problems internal to a block
 * adding an extra 32767 bytes (the worst case uncompressed block size) is
 * sufficient, to ensure that in the worst case the decompressed data for
 * block will stop the byte before the compressed data for a block begins.
 * To avoid problems with the compressed data's meta information an extra 18
 * bytes are needed.  Leading to the formula:
 *
 * extra_bytes = (uncompressed_size >> 12) + 32768 + 18 + decompressor_size.
 *
 * Adding 8 bytes per 32K is a bit excessive but much easier to calculate.
 * Adding 32768 instead of 32767 just makes for round numbers.
 * Adding the decompressor_size is necessary as it musht live after all
 * of the data as well.  Last I measured the decompressor is about 14K.
 * 10K of actuall data and 4K of bss.
 *
 */


/*
/*
 * gzip declarations
 * gzip declarations
@@ -29,15 +111,20 @@ typedef unsigned char uch;
typedef unsigned short ush;
typedef unsigned short ush;
typedef unsigned long  ulg;
typedef unsigned long  ulg;


#define WSIZE 0x8000		/* Window size must be at least 32k, */
#define WSIZE 0x80000000	/* Window size must be at least 32k,
				/* and a power of two */
				 * and a power of two
				 * We don't actually have a window just
				 * a huge output buffer so I report
				 * a 2G windows size, as that should
				 * always be larger than our output buffer.
				 */


static uch *inbuf;	/* input buffer */
static uch *inbuf;	/* input buffer */
static uch window[WSIZE];    /* Sliding window buffer */
static uch *window;	/* Sliding window buffer, (and final output buffer) */


static unsigned insize = 0;  /* valid bytes in inbuf */
static unsigned insize;  /* valid bytes in inbuf */
static unsigned inptr = 0;   /* index of next byte to be processed in inbuf */
static unsigned inptr;   /* index of next byte to be processed in inbuf */
static unsigned outcnt = 0;  /* bytes in output buffer */
static unsigned outcnt;  /* bytes in output buffer */


/* gzip flag byte */
/* gzip flag byte */
#define ASCII_FLAG   0x01 /* bit 0 set: file probably ASCII text */
#define ASCII_FLAG   0x01 /* bit 0 set: file probably ASCII text */
@@ -88,8 +175,6 @@ extern unsigned char input_data[];
extern int input_len;
extern int input_len;


static long bytes_out = 0;
static long bytes_out = 0;
static uch *output_data;
static unsigned long output_ptr = 0;


static void *malloc(int size);
static void *malloc(int size);
static void free(void *where);
static void free(void *where);
@@ -99,17 +184,10 @@ static void *memcpy(void *dest, const void *src, unsigned n);


static void putstr(const char *);
static void putstr(const char *);


extern int end;
static unsigned long free_mem_ptr;
static long free_mem_ptr = (long)&end;
static unsigned long free_mem_end_ptr;
static long free_mem_end_ptr;


#define INPLACE_MOVE_ROUTINE  0x1000
#define LOW_BUFFER_START      0x2000
#define LOW_BUFFER_MAX       0x90000
#define HEAP_SIZE             0x3000
#define HEAP_SIZE             0x3000
static unsigned int low_buffer_end, low_buffer_size;
static int high_loaded =0;
static uch *high_buffer_start /* = (uch *)(((ulg)&end) + HEAP_SIZE)*/;


static char *vidmem = (char *)0xb8000;
static char *vidmem = (char *)0xb8000;
static int vidport;
static int vidport;
@@ -150,7 +228,7 @@ static void gzip_mark(void **ptr)


static void gzip_release(void **ptr)
static void gzip_release(void **ptr)
{
{
	free_mem_ptr = (long) *ptr;
	free_mem_ptr = (unsigned long) *ptr;
}
}
 
 
static void scroll(void)
static void scroll(void)
@@ -223,47 +301,26 @@ static void* memcpy(void* dest, const void* src, unsigned n)
 */
 */
static int fill_inbuf(void)
static int fill_inbuf(void)
{
{
	if (insize != 0) {
	error("ran out of input data");
	error("ran out of input data");
	}
	return 0;

	inbuf = input_data;
	insize = input_len;
	inptr = 1;
	return inbuf[0];
}
}


/* ===========================================================================
/* ===========================================================================
 * Write the output window window[0..outcnt-1] and update crc and bytes_out.
 * Write the output window window[0..outcnt-1] and update crc and bytes_out.
 * (Used for the decompressed data only.)
 * (Used for the decompressed data only.)
 */
 */
static void flush_window_low(void)
static void flush_window(void)
{
    ulg c = crc;         /* temporary variable */
    unsigned n;
    uch *in, *out, ch;
    
    in = window;
    out = &output_data[output_ptr]; 
    for (n = 0; n < outcnt; n++) {
	    ch = *out++ = *in++;
	    c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8);
    }
    crc = c;
    bytes_out += (ulg)outcnt;
    output_ptr += (ulg)outcnt;
    outcnt = 0;
}

static void flush_window_high(void)
{
{
	/* With my window equal to my output buffer
	 * I only need to compute the crc here.
	 */
	ulg c = crc;         /* temporary variable */
	ulg c = crc;         /* temporary variable */
	unsigned n;
	unsigned n;
	uch *in, ch;
	uch *in, ch;

	in = window;
	in = window;
	for (n = 0; n < outcnt; n++) {
	for (n = 0; n < outcnt; n++) {
	ch = *output_data++ = *in++;
		ch = *in++;
	if ((ulg)output_data == low_buffer_end) output_data=high_buffer_start;
		c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8);
		c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8);
	}
	}
	crc = c;
	crc = c;
@@ -271,12 +328,6 @@ static void flush_window_high(void)
	outcnt = 0;
	outcnt = 0;
}
}


static void flush_window(void)
{
	if (high_loaded) flush_window_high();
	else flush_window_low();
}

static void error(char *x)
static void error(char *x)
{
{
	putstr("\n\n");
	putstr("\n\n");
@@ -286,66 +337,8 @@ static void error(char *x)
	while(1);	/* Halt */
	while(1);	/* Halt */
}
}


#define STACK_SIZE (4096)
asmlinkage void decompress_kernel(void *rmode, unsigned long end,

			uch *input_data, unsigned long input_len, uch *output)
long user_stack [STACK_SIZE];

struct {
	long * a;
	short b;
	} stack_start = { & user_stack [STACK_SIZE] , __BOOT_DS };

static void setup_normal_output_buffer(void)
{
#ifdef STANDARD_MEMORY_BIOS_CALL
	if (RM_EXT_MEM_K < 1024) error("Less than 2MB of memory");
#else
	if ((RM_ALT_MEM_K > RM_EXT_MEM_K ? RM_ALT_MEM_K : RM_EXT_MEM_K) < 1024) error("Less than 2MB of memory");
#endif
	output_data = (unsigned char *)CONFIG_PHYSICAL_START; /* Normally Points to 1M */
	free_mem_end_ptr = (long)real_mode;
}

struct moveparams {
	uch *low_buffer_start;  int lcount;
	uch *high_buffer_start; int hcount;
};

static void setup_output_buffer_if_we_run_high(struct moveparams *mv)
{
	high_buffer_start = (uch *)(((ulg)&end) + HEAP_SIZE);
#ifdef STANDARD_MEMORY_BIOS_CALL
	if (RM_EXT_MEM_K < (3*1024)) error("Less than 4MB of memory");
#else
	if ((RM_ALT_MEM_K > RM_EXT_MEM_K ? RM_ALT_MEM_K : RM_EXT_MEM_K) < (3*1024)) error("Less than 4MB of memory");
#endif	
	mv->low_buffer_start = output_data = (unsigned char *)LOW_BUFFER_START;
	low_buffer_end = ((unsigned int)real_mode > LOW_BUFFER_MAX
	  ? LOW_BUFFER_MAX : (unsigned int)real_mode) & ~0xfff;
	low_buffer_size = low_buffer_end - LOW_BUFFER_START;
	high_loaded = 1;
	free_mem_end_ptr = (long)high_buffer_start;
	if ( (CONFIG_PHYSICAL_START + low_buffer_size) > ((ulg)high_buffer_start)) {
		high_buffer_start = (uch *)(CONFIG_PHYSICAL_START + low_buffer_size);
		mv->hcount = 0; /* say: we need not to move high_buffer */
	}
	else mv->hcount = -1;
	mv->high_buffer_start = high_buffer_start;
}

static void close_output_buffer_if_we_run_high(struct moveparams *mv)
{
	if (bytes_out > low_buffer_size) {
		mv->lcount = low_buffer_size;
		if (mv->hcount)
			mv->hcount = bytes_out - low_buffer_size;
	} else {
		mv->lcount = bytes_out;
		mv->hcount = 0;
	}
}

asmlinkage int decompress_kernel(struct moveparams *mv, void *rmode)
{
{
	real_mode = rmode;
	real_mode = rmode;


@@ -360,13 +353,25 @@ asmlinkage int decompress_kernel(struct moveparams *mv, void *rmode)
	lines = RM_SCREEN_INFO.orig_video_lines;
	lines = RM_SCREEN_INFO.orig_video_lines;
	cols = RM_SCREEN_INFO.orig_video_cols;
	cols = RM_SCREEN_INFO.orig_video_cols;


	if (free_mem_ptr < 0x100000) setup_normal_output_buffer();
	window = output;  	/* Output buffer (Normally at 1M) */
	else setup_output_buffer_if_we_run_high(mv);
	free_mem_ptr     = end;	/* Heap  */
	free_mem_end_ptr = end + HEAP_SIZE;
	inbuf  = input_data;	/* Input buffer */
	insize = input_len;
	inptr  = 0;

	if (((u32)output - CONFIG_PHYSICAL_START) & 0x3fffff)
		error("Destination address not 4M aligned");
	if (end > ((-__PAGE_OFFSET-(512 <<20)-1) & 0x7fffffff))
		error("Destination address too large");
#ifndef CONFIG_RELOCATABLE
	if ((u32)output != CONFIG_PHYSICAL_START)
		error("Wrong destination address");
#endif


	makecrc();
	makecrc();
	putstr("Uncompressing Linux... ");
	putstr("Uncompressing Linux... ");
	gunzip();
	gunzip();
	putstr("Ok, booting the kernel.\n");
	putstr("Ok, booting the kernel.\n");
	if (high_loaded) close_output_buffer_if_we_run_high(mv);
	return;
	return high_loaded;
}
}
Loading