/joh'liks/ n.,adj. 386BSD

Porting Unix to the 386: A Practical Approach



William & Lynne Jolitz


After loading the kernel program, we enter protected mode to start up the 386BSD kernel, itself a protected mode program like its own applications.




Entering Protected Mode
As you examine the code from protentr.asm in Listing 4 you can see that many different things are being reconciled at once. There are three different kinds of addressing standards being interconverted as needed:

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

  • 20-bit segment: offset pair "real" mode addresses
  • 32-bit absolute or physical addresses
  • 32-bit segment selector: offset protected mode addresses
Protected-mode instructions are being "generated" from within a "real" mode assembler. A descriptor table is encoded in its peculiar and convoluted structure style, which has its base address split into high and low address chunks on separate portions of the descriptor. Note that in some versions of MASM, LIDT/LGDT instructions present undocumented surprises.

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 ]



<<BACK NEXT >>



Copyright 1989, 1990, 2006 TeleMuse Partners, William Jolitz and Lynne Jolitz