Index: kernel/arch/ia32/src/asm.S
===================================================================
--- kernel/arch/ia32/src/asm.S	(revision b8230b99f42baed609487cfaa5737fbb11968cc0)
+++ kernel/arch/ia32/src/asm.S	(revision b80866028cf785c6288386b0a854ae9b684499fc)
@@ -1,41 +1,47 @@
-#
-# Copyright (c) 2001-2004 Jakub Jermar
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# - Redistributions of source code must retain the above copyright
-#   notice, this list of conditions and the following disclaimer.
-# - Redistributions in binary form must reproduce the above copyright
-#   notice, this list of conditions and the following disclaimer in the
-#   documentation and/or other materials provided with the distribution.
-# - The name of the author may not be used to endorse or promote products
-#   derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-
-## very low and hardware-level functions
-
-# Mask for interrupts 0 - 31 (bits 0 - 31) where 0 means that int has no error
-# word and 1 means interrupt with error word
-#define ERROR_WORD_INTERRUPT_LIST 0x00027d00
+/*
+ * Copyright (c) 2001 Jakub Jermar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Very low and hardware-level functions
+ *
+ */
+
+/**
+ * Mask for interrupts 0 - 31 (bits 0 - 31) where 0 means that int
+ * has no error word  and 1 means interrupt with error word
+ *
+ */
+#define ERROR_WORD_INTERRUPT_LIST  0x00027d00
+
+#include <arch/pm.h>
+#include <arch/mm/page.h>
 
 .text
-
 .global paging_on
 .global enable_l_apic_in_msr
-.global interrupt_handlers
 .global memsetb
 .global memsetw
@@ -45,18 +51,17 @@
 .global memcpy_to_uspace
 .global memcpy_to_uspace_failover_address
-
-
-# Wrapper for generic memsetb
+.global early_putchar
+
+/* Wrapper for generic memsetb */
 memsetb:
 	jmp _memsetb
 
-# Wrapper for generic memsetw
+/* Wrapper for generic memsetw */
 memsetw:
 	jmp _memsetw
 
-
-#define MEMCPY_DST	4
-#define MEMCPY_SRC	8
-#define MEMCPY_SIZE	12
+#define MEMCPY_DST   4
+#define MEMCPY_SRC   8
+#define MEMCPY_SIZE  12
 
 /** Copy memory to/from userspace.
@@ -68,36 +73,42 @@
  * or copy_to_uspace().
  *
- * @param MEMCPY_DST(%esp)	Destination address.
- * @param MEMCPY_SRC(%esp)	Source address.
- * @param MEMCPY_SIZE(%esp)	Size.
+ * @param MEMCPY_DST(%esp)  Destination address.
+ * @param MEMCPY_SRC(%esp)  Source address.
+ * @param MEMCPY_SIZE(%esp) Size.
  *
  * @return MEMCPY_DST(%esp) on success and 0 on failure.
+ *
  */
 memcpy:
 memcpy_from_uspace:
 memcpy_to_uspace:
-	movl %edi, %edx			/* save %edi */
-	movl %esi, %eax			/* save %esi */
+	movl %edi, %edx  /* save %edi */
+	movl %esi, %eax  /* save %esi */
 	
 	movl MEMCPY_SIZE(%esp), %ecx
-	shrl $2, %ecx			/* size / 4 */
+	shrl $2, %ecx  /* size / 4 */
 	
 	movl MEMCPY_DST(%esp), %edi
 	movl MEMCPY_SRC(%esp), %esi
 	
-	rep movsl			/* copy whole words */
-
+	/* Copy whole words */
+	rep movsl
+	
 	movl MEMCPY_SIZE(%esp), %ecx
-	andl $3, %ecx			/* size % 4 */
+	andl $3, %ecx  /* size % 4 */
 	jz 0f
 	
-	rep movsb			/* copy the rest byte by byte */
-
-0:
-	movl %edx, %edi
-	movl %eax, %esi
-	movl MEMCPY_DST(%esp), %eax	/* MEMCPY_DST(%esp), success */
-	ret
-	
+	/* Copy the rest byte by byte */
+	rep movsb
+	
+	0:
+	
+		movl %edx, %edi
+		movl %eax, %esi
+		
+		/* MEMCPY_DST(%esp), success */
+		movl MEMCPY_DST(%esp), %eax
+		ret
+
 /*
  * We got here from as_page_fault() after the memory operations
@@ -108,26 +119,31 @@
 	movl %edx, %edi
 	movl %eax, %esi
-	xorl %eax, %eax			/* return 0, failure */
+	
+	/* Return 0, failure */
+	xorl %eax, %eax
 	ret
 
-## Turn paging on
-#
-# Enable paging and write-back caching in CR0.
-#
+/** Turn paging on
+ *
+ * Enable paging and write-back caching in CR0.
+ *
+ */
 paging_on:
 	movl %cr0, %edx
-	orl $(1 << 31), %edx		# paging on
-	# clear Cache Disable and not Write Though
+	orl $(1 << 31), %edx  /* paging on */
+	
+	/* Clear Cache Disable and not Write Though */
 	andl $~((1 << 30) | (1 << 29)), %edx
-	movl %edx,%cr0
+	movl %edx, %cr0
 	jmp 0f
-0:
-	ret
-
-
-## Enable local APIC
-#
-# Enable local APIC in MSR.
-#
+	
+	0:
+		ret
+
+/** Enable local APIC
+ *
+ * Enable local APIC in MSR.
+ *
+ */
 enable_l_apic_in_msr:
 	movl $0x1b, %ecx
@@ -138,13 +154,44 @@
 	ret
 
-# Clear nested flag
-# overwrites %ecx
+/** Clear nested flag
+ *
+ */
 .macro CLEAR_NT_FLAG
 	pushfl
-	pop %ecx
-	and $0xffffbfff, %ecx
-	push %ecx
+	andl $0xffffbfff, (%esp)
 	popfl
-.endm	
+.endm
+
+#define ISTATE_OFFSET_EDX         0
+#define ISTATE_OFFSET_ECX         4
+#define ISTATE_OFFSET_EBX         8
+#define ISTATE_OFFSET_ESI         12
+#define ISTATE_OFFSET_EDI         16 
+#define ISTATE_OFFSET_EBP         20
+#define ISTATE_OFFSET_EAX         24
+#define ISTATE_OFFSET_EBP_FRAME   28
+#define ISTATE_OFFSET_EIP_FRAME   32
+#define ISTATE_OFFSET_GS          36
+#define ISTATE_OFFSET_FS          40
+#define ISTATE_OFFSET_ES          44
+#define ISTATE_OFFSET_DS          48
+#define ISTATE_OFFSET_ERROR_WORD  52
+#define ISTATE_OFFSET_EIP         56
+#define ISTATE_OFFSET_CS          60
+#define ISTATE_OFFSET_EFLAGS      64
+#define ISTATE_OFFSET_ESP         68
+#define ISTATE_OFFSET_SS          72
+
+/*
+ * Size of the istate structure without the hardware-saved part
+ * and without the error word.
+ */
+#define ISTATE_SOFT_SIZE  52
+
+/*
+ * Size of the entire istate structure including the error word and the
+ * hardware-saved part.
+ */
+#define ISTATE_REAL_SIZE  (ISTATE_SOFT_SIZE + 24)
 
 /*
@@ -160,165 +207,426 @@
 sysenter_handler:
 	sti
-	pushl %ebp	# remember user stack
-	pushl %edi	# remember return user address
-
-	pushl %gs	# remember TLS
-
-	pushl %eax	# syscall number
-	subl $8, %esp	# unused sixth and fifth argument
-	pushl %esi	# fourth argument
-	pushl %ebx	# third argument
-	pushl %ecx	# second argument
-	pushl %edx	# first argument
-
+	subl $(ISTATE_REAL_SIZE), %esp
+
+	/*
+	 * Save the return address and the userspace stack in the istate
+	 * structure on locations that would normally be taken by them.
+	 */
+	movl %ebp, ISTATE_OFFSET_ESP(%esp)
+	movl %edi, ISTATE_OFFSET_EIP(%esp)
+
+	/*
+	 * Push syscall arguments onto the stack
+	 */
+	movl %eax, ISTATE_OFFSET_EAX(%esp)
+	movl %ebx, ISTATE_OFFSET_EBX(%esp)
+	movl %ecx, ISTATE_OFFSET_ECX(%esp)
+	movl %edx, ISTATE_OFFSET_EDX(%esp)
+	movl %esi, ISTATE_OFFSET_ESI(%esp)
+	movl %edi, ISTATE_OFFSET_EDI(%esp)	/* observability; not needed */
+	movl %ebp, ISTATE_OFFSET_EBP(%esp)	/* observability; not needed */
+	
+	/*
+	 * Fake up the stack trace linkage.
+	 */
+	movl %edi, ISTATE_OFFSET_EIP_FRAME(%esp)
+	movl $0, ISTATE_OFFSET_EBP_FRAME(%esp)
+	leal ISTATE_OFFSET_EBP_FRAME(%esp), %ebp
+
+	/*
+	 * Save TLS.
+	 */
+	movl %gs, %edx
+	movl %edx, ISTATE_OFFSET_GS(%esp)
+
+	/*
+	 * Switch to kernel selectors.
+	 */
 	movw $16, %ax
 	movw %ax, %ds
 	movw %ax, %es
-
+	
 	cld
 	call syscall_handler
-	addl $28, %esp	# remove arguments from stack
-
-	pop %gs		# restore TLS
-
-	pop %edx	# prepare return EIP for SYSEXIT
-	pop %ecx	# prepare userspace ESP for SYSEXIT
-
-	sysexit		# return to userspace
-
-
-## Declare interrupt handlers
-#
-# Declare interrupt handlers for n interrupt
-# vectors starting at vector i.
-#
-# The handlers setup data segment registers
-# and call exc_dispatch().
-#
-#define INTERRUPT_ALIGN 64
-.macro handler i n
-	
-	.ifeq \i - 0x30     # Syscall handler
-		pushl %ds
-		pushl %es
-		pushl %fs
-		pushl %gs
-		
-		#
-		# Push syscall arguments onto the stack
-		#
-		# NOTE: The idea behind the order of arguments passed in registers is to
-		#       use all scratch registers first and preserved registers next.
-		#       An optimized libc syscall wrapper can make use of this setup.
-		#
-		pushl %eax
-		pushl %ebp
-		pushl %edi
-		pushl %esi
-		pushl %ebx
-		pushl %ecx
-		pushl %edx
-		
-		# we must fill the data segment registers
-		movw $16, %ax
-		movw %ax, %ds
-		movw %ax, %es
+	
+	/*
+	 * Restore TLS.
+	 */
+	movl ISTATE_OFFSET_GS(%esp), %edx
+	movl %edx, %gs
+	
+	/*
+	 * Prepare return address and userspace stack for SYSEXIT.
+	 */
+	movl ISTATE_OFFSET_EIP(%esp), %edx
+	movl ISTATE_OFFSET_ESP(%esp), %ecx
+
+	addl $(ISTATE_REAL_SIZE), %esp
+	
+	sysexit   /* return to userspace */
+
+/** Declare interrupt handlers
+ *
+ * Declare interrupt handlers for n interrupt
+ * vectors starting at vector i.
+ *
+ */
+
+.macro handler i
+.global int_\i
+int_\i:
+	.ifeq \i - 0x30
+		/* Syscall handler */
+		subl $(ISTATE_SOFT_SIZE + 4), %esp
+
+		/*
+		 * Push syscall arguments onto the stack
+		 *
+		 * NOTE: The idea behind the order of arguments passed
+		 *       in registers is to use all scratch registers
+		 *       first and preserved registers next. An optimized
+		 *       libc syscall wrapper can make use of this setup.
+		 *       The istate structure is arranged in the way to support
+		 *       this idea.
+		 *
+		 */
+		movl %eax, ISTATE_OFFSET_EAX(%esp)
+		movl %ebx, ISTATE_OFFSET_EBX(%esp)
+		movl %ecx, ISTATE_OFFSET_ECX(%esp)
+		movl %edx, ISTATE_OFFSET_EDX(%esp)
+		movl %edi, ISTATE_OFFSET_EDI(%esp)
+		movl %esi, ISTATE_OFFSET_ESI(%esp)
+		movl %ebp, ISTATE_OFFSET_EBP(%esp)
+
+		/*
+		 * Save the selector registers.
+		 */
+		movl %gs, %ecx
+		movl %fs, %edx
+
+		movl %ecx, ISTATE_OFFSET_GS(%esp)
+		movl %edx, ISTATE_OFFSET_FS(%esp)
+
+		movl %es, %ecx
+		movl %ds, %edx
+		
+		movl %ecx, ISTATE_OFFSET_ES(%esp)
+		movl %edx, ISTATE_OFFSET_DS(%esp)
+
+		/*
+		 * Switch to kernel selectors.
+		 */
+		movl $16, %eax
+		movl %eax, %ds
+		movl %eax, %es
+		
+		movl $0, ISTATE_OFFSET_EBP_FRAME(%esp)
+		movl ISTATE_OFFSET_EIP(%esp), %eax
+		movl %eax, ISTATE_OFFSET_EIP_FRAME(%esp)
+		leal ISTATE_OFFSET_EBP_FRAME(%esp), %ebp
 		
 		cld
 		sti
-		# syscall_handler(edx, ecx, ebx, esi, edi, ebp, eax)
+		
+		/* Call syscall_handler(edx, ecx, ebx, esi, edi, ebp, eax) */
 		call syscall_handler
-		cli
-		addl $28, %esp         # clean-up of parameters
-		
-		popl %gs
-		popl %fs
-		popl %es
-		popl %ds
-		
+			
 		CLEAR_NT_FLAG
+
+		/*
+		 * Restore the selector registers.
+		 */
+		movl ISTATE_OFFSET_GS(%esp), %ecx
+		movl ISTATE_OFFSET_FS(%esp), %edx
+
+		movl %ecx, %gs
+		movl %edx, %fs
+
+		movl ISTATE_OFFSET_ES(%esp), %ecx
+		movl ISTATE_OFFSET_DS(%esp), %edx
+			
+		movl %ecx, %es
+		movl %edx, %ds
+			
+		/*
+		 * Restore the preserved registers the handler cloberred itself
+		 * (i.e. EBP).
+		 */
+		movl ISTATE_OFFSET_EBP(%esp), %ebp
+			
+		addl $(ISTATE_SOFT_SIZE + 4), %esp
 		iret
+		
 	.else
 		/*
-		 * This macro distinguishes between two versions of ia32 exceptions.
-		 * One version has error word and the other does not have it.
-		 * The latter version fakes the error word on the stack so that the
-		 * handlers and istate_t can be the same for both types.
+		 * This macro distinguishes between two versions of ia32
+		 * exceptions. One version has error word and the other
+		 * does not have it. The latter version fakes the error
+		 * word on the stack so that the handlers and istate_t
+		 * can be the same for both types.
 		 */
 		.iflt \i - 32
 			.if (1 << \i) & ERROR_WORD_INTERRUPT_LIST
 				/*
-				 * With error word, do nothing
+				 * Exception with error word: do nothing
 				 */
 			.else
 				/*
-				 * Version without error word
+				 * Exception without error word: fake up one
 				 */
-				subl $4, %esp
+				pushl $0
 			.endif
 		.else
 			/*
-			 * Version without error word
+			 * Interrupt: fake up one
 			 */
-			subl $4, %esp
+			pushl $0
 		.endif
 		
-		pushl %ds
-		pushl %es
-		pushl %fs
-		pushl %gs
-		
-		pushl %ebp
-		pushl %edx
-		pushl %ecx
-		pushl %eax
-		
-		# we must fill the data segment registers
-		
-		movw $16, %ax
-		movw %ax, %ds
-		movw %ax, %es
-		
-		# stop stack traces here
+		subl $ISTATE_SOFT_SIZE, %esp
+		
+		/*
+		 * Save the general purpose registers.
+		 */
+		movl %eax, ISTATE_OFFSET_EAX(%esp)
+		movl %ebx, ISTATE_OFFSET_EBX(%esp)
+		movl %ecx, ISTATE_OFFSET_ECX(%esp)
+		movl %edx, ISTATE_OFFSET_EDX(%esp)
+		movl %edi, ISTATE_OFFSET_EDI(%esp)
+		movl %esi, ISTATE_OFFSET_ESI(%esp)
+		movl %ebp, ISTATE_OFFSET_EBP(%esp)
+		
+		/*
+		 * Save the selector registers.
+		 */
+		movl %gs, %eax
+		movl %fs, %ebx
+		movl %es, %ecx
+		movl %ds, %edx
+		
+		movl %eax, ISTATE_OFFSET_GS(%esp)
+		movl %ebx, ISTATE_OFFSET_FS(%esp)
+		movl %ecx, ISTATE_OFFSET_ES(%esp)
+		movl %edx, ISTATE_OFFSET_DS(%esp)
+		
+		/*
+		 * Switch to kernel selectors.
+		 */
+		movl $16, %eax
+		movl %eax, %ds
+		movl %eax, %es
+		
+		/*
+		 * Imitate a regular stack frame linkage.
+		 * Stop stack traces here if we came from userspace.
+		 */
+		cmpl $8, ISTATE_OFFSET_CS(%esp)
+		jz 0f
 		xorl %ebp, %ebp
 		
-		pushl %esp          # *istate
-		pushl $(\i)         # intnum
-		call exc_dispatch   # exc_dispatch(intnum, *istate)
-		addl $8, %esp       # Clear arguments from stack
-		
-		CLEAR_NT_FLAG # Modifies %ecx
-		
-		popl %eax
-		popl %ecx
-		popl %edx
-		popl %ebp
-		
-		popl %gs
-		popl %fs
-		popl %es
-		popl %ds
-		
-		# skip error word, no matter whether real or fake
-		addl $4, %esp
-		iret
-	.endif
-	
-	.align INTERRUPT_ALIGN
-	.if (\n- \i) - 1
-		handler "(\i + 1)", \n
+		0:
+		
+			movl %ebp, ISTATE_OFFSET_EBP_FRAME(%esp)
+			movl ISTATE_OFFSET_EIP(%esp), %eax
+			movl %eax, ISTATE_OFFSET_EIP_FRAME(%esp)
+			leal ISTATE_OFFSET_EBP_FRAME(%esp), %ebp
+			
+			cld
+			
+			pushl %esp   /* pass istate address */
+			pushl $(\i)  /* pass intnum */
+			
+			/* Call exc_dispatch(intnum, istate) */
+			call exc_dispatch
+			
+			addl $8, %esp  /* clear arguments from the stack */
+			
+			CLEAR_NT_FLAG
+			
+			/*
+			 * Restore the selector registers.
+			 */
+			movl ISTATE_OFFSET_GS(%esp), %eax
+			movl ISTATE_OFFSET_FS(%esp), %ebx
+			movl ISTATE_OFFSET_ES(%esp), %ecx
+			movl ISTATE_OFFSET_DS(%esp), %edx
+			
+			movl %eax, %gs
+			movl %ebx, %fs
+			movl %ecx, %es
+			movl %edx, %ds
+			
+			/*
+			 * Restore the scratch registers and the preserved
+			 * registers the handler cloberred itself
+			 * (i.e. EBX and EBP).
+			 */
+			movl ISTATE_OFFSET_EAX(%esp), %eax
+			movl ISTATE_OFFSET_EBX(%esp), %ebx
+			movl ISTATE_OFFSET_ECX(%esp), %ecx
+			movl ISTATE_OFFSET_EDX(%esp), %edx
+			movl ISTATE_OFFSET_EBP(%esp), %ebp
+			
+			addl $(ISTATE_SOFT_SIZE + 4), %esp
+			iret
+		
 	.endif
 .endm
 
-# keep in sync with pm.h !!!
-IDT_ITEMS = 64
-.align INTERRUPT_ALIGN
+#define LIST_0_63 \
+	0, 1, 2, 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,\
+	28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,\
+	53,54,55,56,57,58,59,60,61,62,63
+
 interrupt_handlers:
-h_start:
-	handler 0 IDT_ITEMS
-h_end:
-
-.data
-.global interrupt_handler_size
-
-interrupt_handler_size: .long (h_end - h_start) / IDT_ITEMS
+.irp cnt, LIST_0_63 
+	handler \cnt
+.endr
+
+/** Print Unicode character to EGA display.
+ *
+ * If CONFIG_EGA is undefined or CONFIG_FB is defined
+ * then this function does nothing.
+ *
+ * Since the EGA can only display Extended ASCII (usually
+ * ISO Latin 1) characters, some of the Unicode characters
+ * can be displayed in a wrong way. Only newline and backspace
+ * are interpreted, all other characters (even unprintable) are
+ * printed verbatim.
+ *
+ * @param %ebp+0x08 Unicode character to be printed.
+ *
+ */
+early_putchar:
+	
+#if ((defined(CONFIG_EGA)) && (!defined(CONFIG_FB)))
+	
+	/* Prologue, save preserved registers */
+	pushl %ebp
+	movl %esp, %ebp
+	pushl %ebx
+	pushl %esi
+	pushl %edi
+	
+	movl $(PA2KA(0xb8000)), %edi  /* base of EGA text mode memory */
+	xorl %eax, %eax
+	
+	/* Read bits 8 - 15 of the cursor address */
+	movw $0x3d4, %dx
+	movb $0xe, %al
+	outb %al, %dx
+	
+	movw $0x3d5, %dx
+	inb %dx, %al
+	shl $8, %ax
+	
+	/* Read bits 0 - 7 of the cursor address */
+	movw $0x3d4, %dx
+	movb $0xf, %al
+	outb %al, %dx
+	
+	movw $0x3d5, %dx
+	inb %dx, %al
+	
+	/* Sanity check for the cursor on screen */
+	cmp $2000, %ax
+	jb early_putchar_cursor_ok
+	
+		movw $1998, %ax
+	
+	early_putchar_cursor_ok:
+	
+	movw %ax, %bx
+	shl $1, %eax
+	addl %eax, %edi
+	
+	movl 0x08(%ebp), %eax
+	
+	cmp $0x0a, %al
+	jne early_putchar_backspace
+	
+		/* Interpret newline */
+		
+		movw %bx, %ax  /* %bx -> %dx:%ax */
+		xorw %dx, %dx
+		
+		movw $80, %cx
+		idivw %cx, %ax  /* %dx = %bx % 80 */
+		
+		/* %bx <- %bx + 80 - (%bx % 80) */
+		addw %cx, %bx
+		subw %dx, %bx
+		
+		jmp early_putchar_skip
+	
+	early_putchar_backspace:
+	
+		cmp $0x08, %al
+		jne early_putchar_print
+		
+		/* Interpret backspace */
+		
+		cmp $0x0000, %bx
+		je early_putchar_skip
+		
+		dec %bx
+		jmp early_putchar_skip
+	
+	early_putchar_print:
+	
+		/* Print character */
+		
+		movb $0x0e, %ah  /* black background, yellow foreground */
+		stosw
+		inc %bx
+	
+	early_putchar_skip:
+	
+	/* Sanity check for the cursor on the last line */
+	cmp $2000, %bx
+	jb early_putchar_no_scroll
+	
+		/* Scroll the screen (24 rows) */
+		movl $(PA2KA(0xb80a0)), %esi
+		movl $(PA2KA(0xb8000)), %edi
+		movl $960, %ecx
+		rep movsl
+		
+		/* Clear the 24th row */
+		xorl %eax, %eax
+		movl $40, %ecx
+		rep stosl
+		
+		/* Go to row 24 */
+		movw $1920, %bx
+	
+	early_putchar_no_scroll:
+	
+	/* Write bits 8 - 15 of the cursor address */
+	movw $0x3d4, %dx
+	movb $0xe, %al
+	outb %al, %dx
+	
+	movw $0x3d5, %dx
+	movb %bh, %al
+	outb %al, %dx
+	
+	/* Write bits 0 - 7 of the cursor address */
+	movw $0x3d4, %dx
+	movb $0xf, %al
+	outb %al, %dx
+	
+	movw $0x3d5, %dx
+	movb %bl, %al
+	outb %al, %dx
+	
+	/* Epilogue, restore preserved registers */
+	popl %edi
+	popl %esi
+	popl %ebx
+	leave
+	
+#endif
+	
+	ret
+
