|
Listing 4: Protected Mode Entry (file: protentry.asm)
title protentry
; Copyright (c) 1989, 1990 William Jolitz. All rights reserved.
; Written by William Jolitz 7/89
; Redistribution and use in source and binary forms are freely permitted
; provided that the above copyright notice and attribution and date of work
; and this paragraph are duplicated in all such forms.
; THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
; IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
; WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
; protentry(entry,len,addr,...) long entry,len,addr;
; Entered via jump or "ret" (e.g. no return address on stack),
; builds necessary data structures and transfers into 32-bit
; mode, then copies the 32-bit mode program at address "addr"
; and byte length "len" to location 0 and enters the program
; at location "entry". Note that both "entry" and "addr" are
; true 32-bit absolute pointers, NOT segment:offset pairs. It
; is assumed that both the stack and this program will not be
; overwritten in the subsequent copy to 0 of the program to be
; entered, so caller is responsible to place this in a location
; above the program.
;
; Note that this program is position-independant (self relocating).
;
; Any additional args past the necessary three will be passed on the
; stack to the entered program [note: we obviously don't provide a
; "return" address]
;
_TEXT segment byte public 'CODE'
assume cs:_TEXT,ds:nothing
_TEXT ends
Data32 equ 66h ; prefix to toggle 16/32 data operand
JMPFAR equ 0eah ; opcode for JMP intersegment
.186 ; allow use of shl ax,cnt insn
_TEXT segment byte public 'CODE'
_protentry proc far
jmp short relstrt
; Global Descriptor Table contains three descriptors:
; 0h: Null: not used
; 8h: Code: code segment starts at 0 and extents for 4 gbytes
; 10h: Data: data segment starts at 0 and extends for 4 gbytes(overlays code)
GDT:
NullDesc dw 0,0,0,0 ; null descriptor - not used
CodeDesc dw 0FFFFh ; limit at maximum: (bits 15:0)
db 0,0,0 ; base at 0: (bits 23:0)
db 10011111b ; present/priv level 0/code/conforming/readable
db 11001111b ; page granular/default 32-bit/limit(bits 19:16)
db 0 ; base at 0: (bits 31:24)
DataDesc dw 0FFFFh ; limit at maximum: (bits 15:0)
db 0,0,0 ; base at 0: (bits 23:0)
db 10010011b ; present/priv level 0/data/expand-up/writeable
db 11001111b ; page granular/default 32-bit/limit(bits 19:16)
db 0 ; base at 0: (bits 31:24)
; Load Pointers for Tables
; contains 6-byte pointer information for: LIDT, LGDT
; Interrupt Descriptor Table pointer
IDTPtr dw 7FFh ; limit at maximum (allows all 256 interrupts)
dw 0 ; base at 0: (bits 15:0)
dw 0 ; base at 0: (bits 31:16)
; Global Descriptor Table pointer
GDTPtr dw 17h ; limit to three 8 byte selectors(null,code,data)
dw offset GDT ; base address of GDT (bits 15:0)
dw 0h ; base address of GDT (bits 31:16)
; Constructed instruction for entry into 32 bit protected mode
; ljmp far Note
dispat: db Data32 ; 32-bit override prefix
db JMPFAR ; opcode for JMP intersegment
offl dw 0 ; starting address of 32-bit code (low-word)
dw 0h ; starting address (high word of linear address)
dw 8h ; CodeDesc selector=8h
relstrt:
cli ; disable interrupts
; do address fixups
mov ax,ss ; first, make a new 32 bit stack pointer!
mov cx,4
shl ax,cl ; ax now contains segment address low 16 bits
mov bx,ss
mov cx,12
shr bx,cl ; bx now contains segment address high 16 bits
add ax,sp
adc bx,0 ; ax contains esp 15:0, bx contains esp 31:16
mov si,ax ; pass new stack to 32bit mode via si & di
mov di,bx
mov ax,cs
mov cx,4
shl ax,cl ; ax now contains segment address low 16 bits
mov bx,cs
mov cx,12
shr bx,cl ; bx now contains segment address high 16 bits
mov cx,cs:GDTPtr+2
mov dx,bx
add cx,ax
mov cs:GDTPtr+2,cx
adc cs:GDTPtr+4,dx
mov cx, OFFSET(cpydwn)
mov dx,bx
add cx,ax
mov cs:offl,cx ; overflow?
adc cs:offl+2,dx
; Load the descriptor tables
; lidt cs:IDTPtr ; load Interrupt Descriptor Table
db 2eh,0Fh,01h,00011110b
dw offset IDTPtr
; lgdt cs:GDTPtr ; load Global Descriptor Table
db 2Eh,0Fh,01h,00010110b
dw offset GDTPtr
; smsw ax ; put Machine Status Word in AX
db 0fh, 01h, 11100000b
or al,1 ; activate Protection Enable bit
; lmsw ax ; store Machine Status Word, begin protected mode
db 0fh,01h,11110000b
jmp short Next ; flush prefetch queue
; Load the segment registers with approriate descriptor selectors
Next: mov bx,10h ; set segment registers to DataDesc
mov ss,bx ; load SS,DS,ES segment registers with DataDesc
mov ds,bx
mov es,bx
; Load CS via above's constructed ljmp, entering 32 bit protected mode
jmp short dispat
; Finally running in Protected 32-bit Mode
cpydwn:
mov ax,di ; movl %edi,%eax
shl ax,16 ;db 0c1h,0e0h,10h ; shll $16,%eax
db Data32
mov ax,si ; movw %si,%ax
mov sp,ax ; movl %eax,%esp
pop ax ; pop eax ; entry addr
pop cx ; pop ecx ; byte size
pop si ; pop esi ; source address
xor di,di ; xor edi,edi ; destination address
cld
rep movsb ; copy into place
mov sp,si ; movl esp,esi
jmp ax ; jmp eax ; go to entry
_protentry endp
public _protentry
_TEXT ends
end
Our goal with this subroutine is to turn the 386 into a "flat" 32-bit address space, reminiscent of a 68000, and to dispatch to location 0 to execute the above loaded program. Because we don't anticipate using any other descriptors while our stand-alone program runs, the descriptor table itself is abandoned in memory -- probably to be written over during protected-mode program execution. Interrupts are disabled before entry into protected mode. We don't yet know where the interrupt and exception processing code exists in the protected-mode program, so we must leave the IDT uninitialized (zero length). This means that if an exception or interrupt occurs, the processor will spontaneously reset. Thus, the first responsibility of a just-loaded 32-bit program must be to sensibly initialize itself to catch these conditions. Note that the code for entry into protected mode is PIC (Position Independent Code). We can easily overwrite the memory of the bootstrap program itself, so we must arrange to copy this entry into protected-mode code just above our protected-mode program. This insures its survival when we overwrite MS-DOS, and quite possibly our boot program, never to return. Loading this large array of data containing the programs to be executed is a complex task, because many different 64K segments may be used. A "fence-post" error arising from incorrectly maintained far pointers can lead to unpredictable results when the protected mode program runs. Therefore, to verify that the program contents are loaded correctly, we use a simple checksum just before we dispatch to it in protected mode (see "Unix Kernel Load Program", boot.c).
[ Following the PDP-11, but before the PC, the UNIX box and workstation wars broke out. The Z8000, 8086, and NS32000 microprocessors all tried to be the "One". The most horrid of these, as you know, won.
They all had different views of handling memory. My belief is that up until Intel got it mostly right with the 80386, and completely right with the 80486, it was a horse race. Who would invest the money to keep ahead with marketing, product, fab process, and had the sales to get a return on all of it. After the 80486, you didn't need any of the rest, so they faded out and died, last being the 68000, which has the most "running room" on the rest, because it was contemporaneous with the 68000. IBM tried the same with the PowerPC, but AMD has better chances as a competitor to Intel because they understand this like Intel does. Perhaps, as temporarily with the AMD64, even better than Intel, who had another "432 moment" with its Itanium. -wfj ]
|