The attached patch implements support for the transparent call of
functions in 32 bit space from 16 bit code and allows up to 6 parameters
being passed to a 32 bit function. 3 of the parameters are passed via
registers, the other ones via the stack. More parameters can be
supported but would require additional code (afaik, there is not call
for 'ret + %ecx' or equivalent replacement for 'ret + n' with dynamic n)
The core of the patch is in upcall.S where the assembly function is
added that handles the transitioning and the passing of the parameters.
Each function in the 32 bit space that is supposed to be callable from
16 bit space requires an entry in upcall_protos.h, describing its index
in the function call table, the numbers of parameters it takes and the
parameter types themselves. These function entries are then differently
expanded using #defines in the various files from which they are
included and help to build function calls (the stubs in 16 bit space),
the function call table in 32 bit space and declarations of prototypes
to catch mismatch of parameters.
16 bit code that calls the 32 bit function is putting all parameters
onto the stack but the #defines for 16 bit mode cause the function
<function name>_upcall to be called, which is an assembly macro towards
the end of upcall.S and prepares one more parameter in ebx to be passed
to 'upcall'. The ebx register here holds the offset into the function
call table in the lower 16 bits and the number of bytes that were put
onto the stack when the function was called. The latter is necessary to
fix up the stack in 'upcall' when parameters are passed via the stack so
that the original caller sees the stack as if it had called the 32 bit
function directly.
One test function test_highbios is being added that part 2 will then
call from 16 bit space.
Signed-off-by: Stefan Berger <stefanb(a)us.ibm.com>
---
Makefile | 8 +-
src/post.c | 4 +
src/romlayout.S | 1
src/upcall.S | 191
++++++++++++++++++++++++++++++++++++++++++++++++++++
src/upcall.c | 22 +++++
src/upcall.h | 28 +++++++
src/upcall_16bit.c | 5 +
src/upcall_protos.h | 14 +++
8 files changed, 269 insertions(+), 4 deletions(-)
Index: seabios/src/upcall.c
===================================================================
--- /dev/null
+++ seabios/src/upcall.c
@@ -0,0 +1,22 @@
+#include "config.h" // CONFIG_*
+#include "util.h"
+#include "types.h"
+#include "upcall.h"
+
+int
+test_highbios(u8 a, u16 b, u32 c, u8 d, u16 e, int f)
+{
+ dprintf(1,"test at %p\n",test_highbios);
+ dprintf(1,"a=%d,b=%d,c=%d,d=%d,e=%d,f=%d\n",a,b,c,d,e,f);
+ return a*b;
+}
+
+
+#define FUNC(IDX, NUMPARMS, RETTYPE, NAME, PARMS ...) \
+ [IDX] = NAME,
+
+void *function_table[] = {
+#include "upcall_protos.h"
+};
+
+#undef FUNC
Index: seabios/src/upcall.h
===================================================================
--- /dev/null
+++ seabios/src/upcall.h
@@ -0,0 +1,28 @@
+#ifndef UPCALL_H
+#define UPCALL_H
+
+/* the actual jump table */
+extern void *function_table[];
+
+# ifdef SIXTEENBIT
+
+# define FUNC(IDX, NUMPARMS, RETTYPE, NAME, PARMS...) \
+ RETTYPE NAME ## _upcall(PARMS);
+
+# else
+
+# define FUNC(IDX, NUMPARMS, RETTYPE, NAME, PARMS ...) \
+ RETTYPE NAME(PARMS);
+
+# endif
+
+#include "upcall_protos.h"
+#undef FUNC
+
+/*
+ * holds the pointer where the function_table has been relocated to in
+ * the 'high bios' in 32 bit
+ */
+extern void *function_table_ptr VAR16VISIBLE;
+
+#endif /* UPCALL_H */
Index: seabios/src/upcall.S
===================================================================
--- /dev/null
+++ seabios/src/upcall.S
@@ -0,0 +1,191 @@
+; .code16gcc
+
+// The maximum number of call parameters we can handle
+// First 3 are passed via registers, the rest via stack
+#define MAX_FUNC_ARGS 6
+#define MAX_ARG_BYTES ((MAX_FUNC_ARGS - 3) * 4)
+
+// Call a 32 bit function from 16bit mode via a stub in 16bit mode.
+// Clobbers: ecx
+// Result: eax
+ DECLFUNC upcall
+upcall:
+
+#define CHUNK0 ( 4 ) /* for the already pushed %ebx in the macro */
+
+ pushl %esi // protect
+
+ pushl %edx // 2nd parameter
+ pushl %eax // 1st parameter (will hold result)
+ pushl %ecx // 3rd parameter (clobbered)
+
+ pushf
+#define CHUNK1 (5 * 4)
+
+ // Disable irqs (and clear direction flag)
+ cli
+ cld
+
+ // Disable nmi
+ movl $CMOS_RESET_CODE|NMI_DISABLE_BIT, %eax
+ outb %al, $PORT_CMOS_INDEX
+ inb $PORT_CMOS_DATA, %al
+
+ // enable a20
+ inb $PORT_A20, %al
+ orb $A20_ENABLE_BIT, %al
+ outb %al, $PORT_A20
+
+ push %ds
+ push %es
+ push %esp
+#define CHUNK2 (2 * 2 + 1 * 4)
+
+ // assume ss = 0
+ and $0xffff, %esp
+
+ // Set segment descriptors
+ lidtw %cs:pmode_IDT_info
+ lgdtw %cs:rombios32_gdt_48
+
+ // Enable protected mode
+ movl %cr0, %eax
+ orl $CR0_PE, %eax
+ movl %eax, %cr0
+
+ ljmpl $SEG32_MODE32_CS, $(BUILD_BIOS_ADDR + 1f)
+
+ .code32
+1:
+ // init data segments
+ movl $SEG32_MODE32_DS, %eax
+ movw %ax, %ds
+ movw %ax, %es
+ movw %ax, %ss
+ movw %ax, %fs
+ movw %ax, %gs
+
+ // remember function index and num stack parms
+ pushl %ebx
+#define CHUNK3 ( 4 )
+
+ // remember stackpointer to help against 'ret + n'
+ movl %esp, %esi
+
+#if MAX_ARG_BYTES > 0
+ // Copy 32 bit aguments from original 16 bit call
+ movl $MAX_ARG_BYTES/4, %ecx
+2:
+ pushl MAX_ARG_BYTES + CHUNK3 + CHUNK2 + CHUNK1 + CHUNK0(%esp)
+ loop 2b
+#endif
+
+ // call function in call table
+ mov (BUILD_BIOS_ADDR + function_table_ptr), %eax
+
+ // add offset into table
+ and $0xffff, %ebx
+ add %eax, %ebx
+
+ // load registers for register passing
+ movl MAX_ARG_BYTES + CHUNK3 + CHUNK2 +12(%esp), %edx
+ movl MAX_ARG_BYTES + CHUNK3 + CHUNK2 + 8(%esp), %eax
+ movl MAX_ARG_BYTES + CHUNK3 + CHUNK2 + 4(%esp), %ecx
+
+ // call function
+ call *(%ebx)
+
+ // Result to ecx
+ movl %eax, %ecx
+
+ // load expected stack with all parameters undone
+ movl %esi, %esp
+
+ // get previous ebx holding expected size of stack parms in upper
+ // 16 bit
+ popl %edx
+ shrl $16, %edx
+
+ // Return to real mode
+ ljmpw $SEG32_MODE16_CS, $3f
+
+ .code16gcc
+3:
+ // Disable protected mode
+ movl %cr0, %eax
+ andl $~CR0_PE, %eax
+ movl %eax, %cr0
+
+ // far jump to flush CPU queue after transition to real mode
+ ljmpw $SEG_BIOS, $4f
+
+4:
+ // restore IDT to normal real-mode defaults
+ lidtw %cs:rmode_IDT_info
+
+ // Clear segment registers
+ xorw %ax, %ax
+ movw %ax, %fs
+ movw %ax, %gs
+ movw %ax, %ss
+
+ popl %esp
+ pop %es
+ pop %ds
+ popf
+
+ // result to eax
+ mov %ecx, %eax
+ // expected size of stack parms into ecx
+ mov %edx, %ecx
+
+ addl $8, %esp // skip ecx, eax
+ popl %edx
+ popl %esi
+
+ popl %ebx // pushed by Upcall macro
+
+ // caller expects us to undo stack parameters
+ cmpl $0x0, %ecx
+ jne 5f
+ retl // for 0,1,2,3 parameters
+
+5:
+ cmpl $0x4, %ecx
+ jne 6f
+ retl $0x4 // for 4 parameters
+
+6:
+ cmpl $0x8, %ecx
+ jne 7f
+ retl $0x8 // for 5 parameters
+
+7:
+#if MAX_FUNC_ARGS > 6
+# error Need explicit code here to handle more parameters
+#endif
+ retl $0xc // for 6 parameters
+
+
+// The upcall macro loads ebx with the offset of the function call
+// (in the function_table) into the lower 16 bits and loads the size of the
+// parameters on the stack (beyond 3 parameters) into the upper 16 bits.
+ .macro doUpcall funcname idx numargs
+ DECLFUNC \funcname
+\funcname:
+ pushl %ebx
+.if \numargs > 3
+ movl $(\idx << 2 + (\numargs-3) << 18),%ebx
+.else
+ movl $(\idx << 2), %ebx
+.endif
+ jmp upcall
+ .endm
+
+ .code16gcc
+#define FUNC(IDX, NUMPARMS, RETTYPE, NAME, PARMS ...) \
+ doUpcall NAME ## _upcall IDX NUMPARMS
+
+#include "upcall_protos.h"
+
+#undef FUNC
Index: seabios/src/upcall_protos.h
===================================================================
--- /dev/null
+++ seabios/src/upcall_protos.h
@@ -0,0 +1,12 @@
+/* this file is included by multiple files and expands the #defines
+ differently each time */
+
+#ifdef SIXTEENBIT
+
+/* all function names below must be expanded with _upcall */
+
+# define test_highbios test_highbios_upcall
+
+#endif
+
+FUNC(0, 6, int , test_highbios, u8 a, u16 b, u32 c,u8 d, u16 e, int f)
Index: seabios/src/upcall_16bit.c
===================================================================
--- /dev/null
+++ seabios/src/upcall_16bit.c
@@ -0,0 +1,5 @@
+#include "config.h" // CONFIG_*
+#include "types.h"
+
+/* anchor to high bios */
+void *function_table_ptr VAR16VISIBLE = NULL;
Index: seabios/src/romlayout.S
===================================================================
--- seabios.orig/src/romlayout.S
+++ seabios/src/romlayout.S
@@ -19,6 +19,7 @@
#include "cmos.h" // CMOS_RESET_CODE
#include "../out/asm-offsets.h" // BREGS_*
#include "entryfuncs.S" // ENTRY_*
+#include "upcall.S"
/****************************************************************
Index: seabios/src/post.c
===================================================================
--- seabios.orig/src/post.c
+++ seabios/src/post.c
@@ -24,6 +24,7 @@
#include "paravirt.h" // qemu_cfg_port_probe
#include "ps2port.h" // ps2port_setup
#include "virtio-blk.h" // virtio_blk_setup
+#include "upcall.h" // function_table
/****************************************************************
@@ -307,6 +308,9 @@ reloc_init(void)
dprintf(1, "Relocating init from %p to %p (size %d)\n"
, code32init_start, dest, initsize);
s32 delta = dest - (void*)code32init_start;
+
+ function_table_ptr = (void *)&function_table + delta;
+
memcpy(dest, code32init_start, initsize);
updateRelocs(dest, _reloc_abs_start, _reloc_abs_end, delta);
updateRelocs(dest, _reloc_rel_start, _reloc_rel_end, -delta);
Index: seabios/Makefile
===================================================================
--- seabios.orig/Makefile
+++ seabios/Makefile
@@ -16,11 +16,11 @@ SRCBOTH=misc.c pmm.c stacks.c output.c u
pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \
usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \
virtio-ring.c virtio-pci.c virtio-blk.c apm.c
-SRC16=$(SRCBOTH) system.c disk.c font.c
+SRC16=$(SRCBOTH) system.c disk.c font.c upcall_16bit.c
SRC32FLAT=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \
acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \
lzmadecode.c bootsplash.c jpeg.c usb-hub.c paravirt.c dev-i440fx.c \
- pci_region.c
+ pci_region.c upcall.c
SRC32SEG=util.c output.c pci.c pcibios.c apm.c stacks.c
cc-option = $(shell if test -z "`$(1) $(2) -S -o /dev/null -xc \
@@ -39,11 +39,11 @@ COMMONCFLAGS += $(call cc-option,$(CC),-
COMMONCFLAGS += $(call cc-option,$(CC),-fno-stack-protector-all,)
CFLAGS32FLAT = $(COMMONCFLAGS) -g -DMODE16=0 -DMODESEGMENT=0
-CFLAGSSEG = $(COMMONCFLAGS) -DMODESEGMENT=1 -fno-defer-pop \
+CFLAGSSEG = $(COMMONCFLAGS) -DMODESEGMENT=1 -DSIXTEENBIT -fno-defer-pop \
$(call
cc-option,$(CC),-fno-jump-tables,-DMANUAL_NO_JUMP_TABLE) \
$(call cc-option,$(CC),-fno-tree-switch-conversion,)
CFLAGS32SEG = $(CFLAGSSEG) -DMODE16=0 -g
-CFLAGS16INC = $(CFLAGSSEG) -DMODE16=1 \
+CFLAGS16INC = $(CFLAGSSEG) -DMODE16=1 -DSIXTEENBIT \
$(call cc-option,$(CC),--param
large-stack-frame=4,-fno-inline)
CFLAGS16 = $(CFLAGS16INC) -g