Category : Assembly Language Source Code
Archive   : ATPROTMD.ZIP
Filename : PM_AT.ASM

 
Output of file : PM_AT.ASM contained in archive : ATPROTMD.ZIP
NAME PM_AT
PAGE 60, 132
.286C
;
; PM/AT - A program to place the PC/AT into Protected Mode
; Copyright 1985, Ross P. Nelson
;

INCLUDE \usr\include\protect.inc

; Data structure definitions
DESCRIP STRUC ; generic descriptor format
limit DW ? ; offset if gate
phys_addr_lo DW ? ; selector if gate
phys_addr_hi DB ? ; wc if gate
access DB ? ; access rights
DW 0 ; reserved for 386
DESCRIP ENDS

TSS_BLOCK STRUC ; format of a TSS
back_link DW ? ; previously active TSS
rSP0 DW ? ; level 0 stack
rSS0 DW ?
rSP1 DW ? ; level 1 stack
rSS1 DW ?
rSP2 DW ? ; level 2 stack
rSS2 DW ?
rIP DW ?
FLAGS DW ?
rAX DW ?
rCX DW ?
rDX DW ?
rBX DW ?
rSP DW ?
rBP DW ?
rSI DW ?
rDI DW ?
rES DW ?
rCS DW ?
rSS DW ? ; active stack segment
rDS DW ?
task_LDT DW ? ; LDT selector
TSS_BLOCK ENDS

; Literal values for descriptor types
TSS EQU 1
LDT EQU 2
TSS_BUSY EQU 3
CALL_GATE EQU 4
TASK_GATE EQU 5
INT_GATE EQU 6
TRAP_GATE EQU 7

RDONLY EQU 0 ; read only
RD_WR EQU 1 ; read/write
RD_WR_XD EQU 3 ; read/write expand down
EXONLY EQU 4 ; execute only
EX_RD EQU 5 ; execute/readable
EXONLY_CF EQU 6 ; execute only/conforming
EX_RD_CF EQU 7 ; execute/readable/conforming

TSS_LIMIT EQU 43

; Segment building macros
MSEG MACRO name,type,priv,combine ;; start a memory segment
name SEGMENT PARA combine ;; MASM directive
zero = $ ;; for ALIGN macro
&name&_start = $ ;; origin
&name&_ar = 90h OR (priv SHL 5) OR (type SHL 1) ;; access rights
ENDM

SSEG MACRO name,type,priv ;; start a system segment
name SEGMENT PARA ;; MASM directive
zero = $ ;; for ALIGN macro
&name&_start = $ ;; origin
&name&_ar = 80h OR (priv SHL 5) OR type ;; access rights
ENDM

ENDSEG MACRO name ;; terminate a segment
&name&_limit = $ - &name&_start - 1 ;; create variable for seg limit
name ENDS ;; limit <- size-1 (0-FFFFh)
ENDM

; Descriptor building macros
DSCRP MACRO export,name ;; build descrip for segment
IFDIF ,<> ;; check for export name
export LABEL WORD
ENDIF
DW &name&_limit ;; segment limit
DW name ;; 16-bit segment addr
DB 0 ;; high order addr
DB &name&_ar ;; access rights
DW 0 ;; reserved
ENDM

GATE MACRO export,offset,select,wc,type,priv ;; build descriptor
IFDIF ,<> ;; check for export name
export LABEL WORD
ENDIF
DW offset ;; offset
DW select ;; segment selector
DB wc ;; word count
DB 80h OR (priv SHL 5) + type ;; access rights
DW 0 ;; reserved
ENDM

; Selector creating macros for Task segments
GDT_SEL MACRO sel,priv
DW sel + priv ;; assume sel = index * 8
ENDM

LDT_SEL MACRO sel,priv
DW sel + 4 + priv ;; like GDT but TI bit set
ENDM

; Utility macros
CALL_EX MACRO sel,rpl ;; call exported item
DB 9Ah ;; FAR call
DW 0 ;; no offset
DW sel + rpl ;; selector with req. priv.
ENDM

ALIGN MACRO bound ;; align $ on power of 2 bounds
LOCAL diff
diff = (($ - zero) AND (bound - 1)) ;; distance from bound
IF diff NE 0 ;; if on bound skip
ORG $ + (bound - diff) ;; else adjust
ENDIF
ENDM

PAGE
; This segment contains the Global Descriptor Table

MSEG GDT,RD_WR,0
; Required by INT 15
DESCRIP <0,0,0,0> ; GDT(0) always blank
DSCRP int15_gdt_dat,GDT ; DATA -> GDT
DSCRP int15_idt_dat,IDT ; DATA -> IDT
DSCRP ,DSC ; DATA -> DS
DSCRP ,DSC ; DATA -> ES
DSCRP ,DSC ; STACK -> SS
DSCRP ,INIT ; CODE -> CS
DESCRIP <0,0,0,0> ; CODE -> BIOS/int 15 reserved
DSCRP setup_tss,INIT_TSS ; TSS -> initial task
; Mini BIOS
DSCRP bio_dat,MBDAT ; DATA -> mini bios
DSCRP bios_seg,BIOS ; CODE -> mini bios
DSCRP disp_mono,MONO_RAM ; DATA -> monochrome display
DSCRP disp_color,COLOR_RAM ; DATA -> color display
; Fault handlers
DSCRP task_df,FTASK8 ; TSS -> double fault
xtra8 DESCRIP ; writable DATA alias for TSS
DSCRP task_tf,FTASK10 ; TSS -> task fault
xtra10 DESCRIP ; writable DATA alias for TSS
DSCRP task_sf,FTASK12 ; TSS -> task fault
xtra12 DESCRIP ; writable DATA alias for TSS
DSCRP fault_dat,FDAT ; DATA -> handler
DSCRP fhandler,HAND ; CODE -> handler
DSCRP falias,FDAT ; free for fault handler use
; Shared library
DSCRP share_lib,SHLIB ; CODE -> shared
GATE share_gate,shlib_start,share_lib,0,CALL_GATE,3 ; GATE to code
; Second task
DSCRP task2_tss,TASK2 ; TSS for 2nd task
DSCRP task2_ldt,T2LDT ; LDT for 2nd task
; Future use
DESCRIP <0,0,0,0> ; available
DESCRIP <0,0,0,0> ; available
DESCRIP <0,0,0,0> ; available
DESCRIP <0,0,0,0> ; available
ENDSEG GDT

PAGE
; This segment contains the Interrupt Descriptor Table.

MSEG IDT,RD_WR,0
; Chip level interrupts (0 - 1Fh)
GATE ,fault_00,fhandler,0,TRAP_GATE,0 ; DIVIDE
GATE ,fault_01,fhandler,0,TRAP_GATE,0 ; TRAP
GATE ,fault_02,fhandler,0,TRAP_GATE,0 ; NMI
GATE ,fault_03,fhandler,0,TRAP_GATE,0 ; BRKPT
GATE ,fault_04,fhandler,0,TRAP_GATE,0 ; INTO
GATE ,fault_05,fhandler,0,TRAP_GATE,0 ; BOUND
GATE ,fault_06,fhandler,0,TRAP_GATE,0 ; undef
GATE ,fault_07,fhandler,0,TRAP_GATE,0 ; 287 NAVAIL
GATE ,0,task_df,0,TASK_GATE,0 ; DBL FAULT
GATE ,fault_09,fhandler,0,TRAP_GATE,0 ; 287 OVRRUN
GATE ,0,task_tf,0,TASK_GATE,0 ; TSS FAULT
GATE ,fault_11,fhandler,0,TRAP_GATE,0 ; NP FAULT
GATE ,0,task_sf,0,TASK_GATE,0 ; STACK FAULT
GATE ,fault_13,fhandler,0,TRAP_GATE,0 ; GP FAULT
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,fault_16,fhandler,0,TRAP_GATE,0 ; 287 ERROR
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
; System interrupts
; Hardware Level 0 (20-27) DOS equivalent vector
GATE ,timer_int,bios_seg,0,INT_GATE,0 ; 8
GATE ,kb_int,bios_seg,0,INT_GATE,0 ; 9
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; A
GATE ,com1_int,bios_seg,0,INT_GATE,0 ; B
GATE ,com2_int,bios_seg,0,INT_GATE,0 ; C
GATE ,prn2_int,bios_seg,0,INT_GATE,0 ; D
GATE ,fd_int,bios_seg,0,INT_GATE,0 ; E
GATE ,prn1_int,bios_seg,0,INT_GATE,0 ; F
; Hardware Level 1 (28-2F)
GATE ,rtc_int,bios_seg,0,INT_GATE,0 ; 70
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 71
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 72
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 73
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 74
GATE ,n287_int,bios_seg,0,INT_GATE,0 ; 75
GATE ,hd_int,bios_seg,0,INT_GATE,0 ; 76
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 77
; Mini BIOS (30 - 31)
GATE ,int_30,bios_seg,0,TRAP_GATE,3
GATE ,sw_reset,bios_seg,0,TRAP_GATE,0
ENDSEG IDT

PAGE
; Mini BIOS
; This section contains the "miniBIOS," a collection of
; routines for hardware support, including the interrupt
; handlers, and user-callable display routines.

; PC/AT Hardware Control
MSEG MONO_RAM,RD_WR,0,
ORG 4000 ; end of monochrome RAM
ENDSEG MONO_RAM

MSEG COLOR_RAM,RD_WR,0,
ORG 16 * 1024 ; end of color RAM
ENDSEG COLOR_RAM

MASTER EQU 20h ; master 8259A
SLAVE EQU 0A0h ; slave 8259A
DEV_COLOR EQU 3D4h ; color port
RETRACE_PORT EQU 3DAh ; port for horiz/vert retrace
DEV_MONO EQU 3B4h ; monochrome port
DEV_RTC EQU 70h ; real-time-clock port

EOI EQU 20h ; end of interrupt command

WR_DEVICE MACRO device,unit,data ; write to rtc or crt devices
IFDIF ,<>
mov dx, device
ENDIF
mov al, unit
out dx, al
inc dx
mov al, data
out dx, al
dec dx
ENDM


MSEG MBDAT,RD_WR,0

tick_ctr DD 0 ; incremented by timer int
kb_ctr DW -2 ; keyboard interrupts
; NOTE: This program is setup to run on a monochrome system only
; This pointer must be modified to support a color display.
display_ptr LABEL DWORD ; points to display RAM
DW 0 ; offset
DW disp_mono ; selector (MONOCHROME)
cursor LABEL word
cursor_x DB 0 ; column
cursor_y DB 0 ; row
attrib DB 7
ENDSEG MBDAT

MSEG BIOS,EXONLY,0
ASSUME CS:BIOS, DS:NOTHING

; INTERRUPT HANDLERS
; This is where the MINIBIOS comes when it gets a hardware interrupt.
; In this implementation, the only interrupt which is handled is
; the timer tick. The keyboard interrupt is also used as a signal
; to exit protected mode. The other handlers are left as an
; exercise for the user.
; Level 0 interrupts
timer_int:
push ax
push ds
mov ax, OFFSET bio_dat ; data seg selector
mov ds, ax
ASSUME DS:MBDAT
inc WORD PTR tick_ctr ; bump counter
adc WORD PTR tick_ctr[2], 0
mov al, EOI ; signal 8259A
out MASTER, al
pop ds
ASSUME DS:NOTHING
pop ax
iret

kb_int:
push ax
mov al, EOI
out MASTER, al
pop ax
int 31h ; RESET system
iret

com1_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret

com2_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret

prn2_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret

fd_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret

prn1_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret

; Level 1 interrupts - must EOI both the SLAVE and MASTER 8259As
rtc_int:
push ax
mov al, EOI
out SLAVE, al
out MASTER, al
pop ax
iret

n287_int:
push ax
mov al, EOI
out SLAVE, al
out MASTER, al
pop ax
iret

hd_int:
push ax
mov al, EOI
out SLAVE, al
out MASTER, al
pop ax
iret

rsrv_int:
int 1Fh ; cause failure

PAGE

; MiniBIOS user callable function codes
MBIOS_WR_CHAR EQU 0
MBIOS_WR_STRING EQU 1
MBIOS_WR_CRSR EQU 2
MBIOS_WR_ATTR EQU 3
MBIOS_BELL EQU 4
MBIOS_CLS EQU 5

; USER CALLABLE FUNCTIONS
; INT 30h
; Write to display -- All registers but AX preserved
; FN: AH = 0 Write character
; Input: AL = char
;
; FN: AH = 1 Write ASCIIZ string
; Input DS:SI -> string
;
; FN: AH = 2 Set cursor
; Input: DH = row
; DL = column
;
; FN: AH = 3 Set attribute
; Input: AL = attribute
;
; FN: AH = 4 Bell
;
; FN AH = 5 Clear Screen
;
int_30:
cld
or ah, ah ; determine function
jz co
dec ah
jnz $ + 5
jmp linout
dec ah
jnz $ + 5
jmp set_cursor
dec ah
jnz $ + 5
jmp set_attrib
dec ah
jnz $ + 5
jmp bell
dec ah
jnz $ + 5
jmp cls
iret

wr_cursor PROC NEAR
; Write HW cursor - cursor in DX, trashes AX, CX, DX
mov ax, 80 ; convert to 16 bit
mul dh
xor dh, dh
add dx, ax
mov cx, dx
WR_DEVICE DEV_MONO,14,ch ; write hardware
WR_DEVICE ,15,cl
ret
wr_cursor ENDP

co:
push cx ; save state
push di
push ds
push es
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
les di, display_ptr
mov ch, al ; save character
mov ax, 80 * 2 ; number of columns/row
mov cl, cursor_y ; time #rows
mul cl
add di, ax ; update offset
xor ax, ax ; zero
mov al, cursor_x ; column
shl al, 1 ; * 2
add di, ax ; update offset
mov al, ch ; restore character
mov ah, attrib ; get data
stosw
inc cursor_x ; ajust cursor position
cmp cursor_x, 80
jb co_done
sub cursor_x, 80
inc cursor_y
co_done: push dx
mov dx, cursor
call wr_cursor
pop dx
pop es
pop ds
ASSUME DS:NOTHING
pop di
pop cx
iret

linout:
push es ; save state
push si
push di
push cx
push ds
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
les di, display_ptr ; get screen pointer
mov ax, 80 * 2 ; number of columns/row
mov cl, cursor_y ; time #rows
mul cl
add di, ax ; update offset
xor ax, ax ; zero
mov al, cursor_x ; column
shl al, 1 ; * 2
add di, ax ; update offset
mov ah, attrib ; screen attribute
pop ds ; user data
ASSUME DS:NOTHING
xor cx, cx ; count
cld
linout_loop: lodsb
or al, al ; end of string?
jz line_done ; yes - quit loop
stosw ; no - write char/attrib
inc cx
jmp linout_loop ; write next char
line_done: push ds
mov ax, OFFSET bio_dat ; bios data segment
mov ds, ax
ASSUME DS:MBDAT
mov ax, cx ; count
mov cl, 80
div cl ; al = rows, ah = columns
add cursor_x, ah
cmp cursor_x, 80 ; overflow?
jb update_row ; no
sub cursor_x, 80 ; else adjust
inc al
update_row: add cursor_y, al
push dx
mov dx, cursor
call wr_cursor
pop dx
pop ds
ASSUME DS:NOTHING
pop cx
pop di ; start of chars written
pop si ; restore state
pop es
iret ; and return

set_cursor:
push cx
push dx
push ds
mov ax, OFFSET bio_dat ; bios data segment
mov ds, ax
ASSUME DS:MBDAT
mov cursor, dx ; save new cursor
call wr_cursor
pop ds
ASSUME DS:NOTHING
pop dx
pop cx
iret

set_attrib:
push cx
push ds
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
mov attrib, al
pop ds
ASSUME DS:NOTHING
pop cx
iret

bell:
push ax
push bx
push cx
mov bx, 200
in al, 61h ; get current state
push ax ; save it
bell_loop: and al, 0FCh ; speaker off
out 61h, al
mov cx, 60
idle1: loop idle1
or al, 002h ; speaker on
out 61h, al
mov cx, 180 ; duty cycle 1:3
idle2: loop idle2
dec bx ; test major loop
jnz bell_loop
pop ax
out 61h, al ; restore state
pop cx
pop bx
pop ax
iret

cls:
push cx ; save state
push dx
push di
push ds
push es
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
les di, display_ptr
mov ah, attrib
mov al, ' '
mov cx, 80 * 25
cld
rep stosw
xor dx, cx
mov cursor, dx
call wr_cursor
pop es
pop ds
pop di
pop dx
pop cx
iret


;
; INT 31
; Reset processor
;
sw_reset PROC FAR
WR_DEVICE DEV_RTC,0Fh,0 ; write SHUTDOWN code to RTC
mov al, 0FEh ; HW SHUTDOWN
out 64h, al ; HW STATUS
halt: hlt
jmp halt
sw_reset ENDP
ENDSEG BIOS

PAGE
; FAULT HANDLERS
; In this prototype system, all the fault handler does is to display
; the name and location of the fault on the screen for a short period
; of time before resetting the system. This should provide the user
; with enough information to correct the problem.

; TSS for #DF - double fault handler
; #DF must have its own task to prevent shutdown
SSEG FTASK8,TSS,0
DW 0 ; back link
DW 0, 0 ; SS0:SP - unneeded/CPL=0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW fault_ts ; IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW fhandler_stack ; SP
DW fhandler_stack ; BP
DW msg_08, 0 ; SI/DI
GDT_SEL xtra8,0 ; ES
GDT_SEL fhandler,0 ; CS
GDT_SEL fault_dat,0 ; SS
GDT_SEL fault_dat,0 ; DS
DW 0 ; LDT selector
ENDSEG FTASK8

; TSS for #TF - task fault handler
; #TF must have its own task to ensure a valid machine state
SSEG FTASK10,TSS,0
DW 0 ; back link
DW 0, 0 ; SS0:SP - unneeded/CPL=0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW fault_ts ; IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW fhandler_stack ; SP
DW fhandler_stack ; BP
DW msg_10, 0 ; SI/DI
GDT_SEL xtra10,0 ; ES
GDT_SEL fhandler,0 ; CS
GDT_SEL fault_dat,0 ; SS
GDT_SEL fault_dat,0 ; DS
DW 0 ; LDT selector
ENDSEG FTASK10

; TSS for #SF - stack fault handler
; #SF requires its own task to prevent #DF in certain occasions
SSEG FTASK12,TSS,0
DW 0 ; back link
DW 0, 0 ; SS0:SP - unneeded/CPL=0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW fault_ts ; IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW fhandler_stack ; SP
DW fhandler_stack ; BP
DW msg_12, 0 ; SI/DI
GDT_SEL xtra12,0 ; ES
GDT_SEL fhandler,0 ; CS
GDT_SEL fault_dat,0 ; SS
GDT_SEL fault_dat,0 ; DS
DW 0 ; LDT selector
ENDSEG FTASK12

MSEG FDAT,RD_WR,0
; Data for fault handlers
msg_00 DB "*** DIVIDE FAULT ***", 0
msg_01 DB "*** SINGLE STEP TRAP ***", 0
msg_02 DB "*** NMI ***", 0
msg_03 DB "*** INT 3 ***", 0
msg_04 DB "*** OVERFLOW EXCEPTION ***", 0
msg_05 DB "*** BOUND EXCEPTION ***", 0
msg_06 DB "*** UNDEFINED OPCODE ***", 0
msg_07 DB "*** 287 NOT AVAILABLE ***", 0
msg_08 DB "*** DOUBLE FAULT ***", 0
msg_09 DB "*** 287 SEGMENT OVERRUN ***", 0
msg_10 DB "*** ILLEGAL TSS FAULT ***", 0
msg_11 DB "*** NOT PRESENT FAULT ***", 0
msg_12 DB "*** STACK FAULT ***", 0
msg_13 DB "*** GENERAL PROTECTION FAULT ***", 0
msg_16 DB "*** 287 EXCEPTION ***", 0
msg_fcode DB "*** Fault code = ",0
msg_faddr DB "*** Fault address = ",0
msg_unknown DB "*** UNKNOWN EXCEPTION ***", 0
msg_buffer DB 40 DUP (0)

ALIGN 2 ; force stack to word boundary
DW 64 DUP (0)
fhandler_stack LABEL WORD

ENDSEG FDAT

MSEG HAND,EXONLY,0
; Code for fault handlers
ASSUME CS:HAND, DS:FDAT

fault_00: mov si, OFFSET msg_00
jmp fail

fault_01: mov si, OFFSET msg_01
jmp fail

fault_02: mov si, OFFSET msg_02
jmp fail

fault_03: mov si, OFFSET msg_03
jmp fail

fault_04: mov si, OFFSET msg_04
jmp fail

fault_05: mov si, OFFSET msg_05
jmp fail

fault_06: mov si, OFFSET msg_06
jmp fail

fault_07: mov si, OFFSET msg_07
jmp fail

fault_08: mov si, OFFSET msg_08
jmp fail

fault_09: mov si, OFFSET msg_09
jmp fail

fault_10: mov si, OFFSET msg_10
jmp fail

fault_11: mov si, OFFSET msg_11
jmp fail

fault_12: mov si, OFFSET msg_12
jmp fail

fault_13: mov si, OFFSET msg_13
jmp fail

fault_16: mov si, OFFSET msg_16
jmp fail

unknown: mov si, OFFSET msg_unknown
jmp fail

; All fault handlers that have a task switch come here
fault_ts:
pop ax ; error code
mov bx, ES:[back_link] ; selector of faulting task
lar dx, bx ; check if accessable
jnz fake_data ; invalid
test dh, 80h ; check present bit
jz fake_data ; invalid
and dh, 1Fh ; mask - leaving type info
cmp dh, TSS_BUSY ; should point to user TSS
jne fake_data ; invalid
lsl dx, bx ; get segment size
cmp dx, TSS_LIMIT ; ensure size OK
jb fake_data ; branch too small
; At this point, we know that the back link points to a
; valid TSS, we now wish to create a readable data segment
; that points to the same physical location as the TSS so
; we can extract some information from it. Since this segment
; is created pointing to the same address as a previously
; existing segment, it is called an ALIAS
mov di, OFFSET falias ; offset of free descriptor
mov dx, OFFSET int15_gdt_dat; selector for GDT as
mov es, dx ; if it were a data seg
and bx, 0FFF8h ; convert selector to offset
mov cx, ES:[bx].phys_addr_lo; get phys addr of user TSS
mov ES:[di].phys_addr_lo, cx; store in free descriptor
mov cl, ES:[bx].phys_addr_hi; continue with high byte
mov ES:[di].phys_addr_hi, cl
mov ES:[di].limit, TSS_LIMIT; complete free descriptor
mov es, di ; use as selector to segment
push ES:[rCS] ; push task's CS
push ES:[rIP] ; push task's IP
push ax ; error code
jmp fail


fake_data: push WORD PTR 0FFFFh ; can't get real info
push WORD PTR 0FFFEh ; push false CS, IP
push ax ; error code
jmp fail


pause PROC NEAR
mov bx, 10
ploop: mov cx, 0FFFFh
loop $
dec bx
jnz ploop
ret
pause ENDP

fail: mov ax, OFFSET fault_dat ; get legal DS
mov ds, ax
mov es, ax
mov dx, 0 ; cursor x=0/y=0
mov ah, MBIOS_WR_CRSR ; home cursor
int 30h
mov ah, MBIOS_WR_STRING ; write msg
int 30h
mov dx, 0100h ; cursor x=0/y=1
mov ah, MBIOS_WR_CRSR ; home cursor
int 30h
; check if error code on stack
cmp si, OFFSET msg_08 ; was DF fault?
je show_code
cmp si, OFFSET msg_10 ; was TF fault?
je show_code
cmp si, OFFSET msg_11 ; was NP fault?
je show_code
cmp si, OFFSET msg_12 ; was SF fault?
je show_code
cmp si, OFFSET msg_13 ; was GP fault?
jne show_addr
show_code: mov si, OFFSET msg_fcode ; print code message
mov ah, MBIOS_WR_STRING
int 30h
pop dx ; get code from stack
mov di, OFFSET msg_buffer
mov ah, LIB_BIN_HEX ; convert to hex
CALL_EX share_gate,0
mov si, OFFSET msg_buffer ; and print
mov ah, MBIOS_WR_STRING
int 30h
mov dx, 0200h ; cursor x=0/y=2
mov ah, MBIOS_WR_CRSR ; home cursor
int 30h
show_addr:
mov si, OFFSET msg_faddr ; print addr message
mov ah, MBIOS_WR_STRING
int 30h
pop bx ; get offset
pop dx ; get segment
push bx ; save offset
mov di, OFFSET msg_buffer
mov ah, LIB_BIN_HEX
CALL_EX share_gate,0
mov si, OFFSET msg_buffer
mov ah, MBIOS_WR_STRING
int 30h
mov al, ':'
mov ah, MBIOS_WR_CHAR
int 30h
pop dx ; offset
mov di, OFFSET msg_buffer
mov ah, LIB_BIN_HEX
CALL_EX share_gate,0
mov si, OFFSET msg_buffer
mov ah, MBIOS_WR_STRING
int 30h
call pause ; wait
call pause
call pause
mov ah, MBIOS_BELL ; bell
int 30h
call pause
mov ah, MBIOS_BELL ; bell
int 30h
call pause
mov ah, MBIOS_BELL ; bell
int 30h
call pause ; wait
call pause
call pause
call pause
int 31h ; reset processor
ENDSEG HAND

PAGE
MSEG SHLIB,EX_RD_CF,0
;
; This segment implements a library of shared functions that may be
; invoked through gate "share_gate". The segment is conforming, so its
; code will run at the same privelege as the caller. The calling
; sequence is merely to set up the registers and CALL the gate. If an
; illegal function number is called, the system issues a DIVIDE BY 0.
; Only registers BP, SP, CS, DS, ES, and SS are guaranteed preserved.
;
ASSUME CS:SHLIB, DS:NOTHING, ES:NOTHING

LIB_SINT_BIN EQU 0
LIB_UINT_BIN EQU 1
LIB_HEX_BIN EQU 2
LIB_BIN_SINT EQU 3
LIB_BIN_UINT EQU 4
LIB_BIN_HEX EQU 5

shlib_code PROC FAR
cld ; set direction for string fns
cmp ah, 5 ; beyond last function?
jbe index ; no - do indexing
xor ax, ax ; zero ax
div al ; force divide fault

index: mov bl, ah ; get FN code
xor bh, bh ; clear high order
shl bx, 1 ; convert FN to index
add bx, OFFSET table
jmp WORD PTR CS:[bx] ; invoke function.

table DW sint_bin
DW uint_bin
DW hex_bin
DW bin_sint
DW bin_uint
DW bin_hex

; Function 0 / ASCII SIGNED INT to BINARY conversion
; AH = 0
; DS:SI -> Null terminated string of digits
; Returns:
; AX <- 16-bit signed integer
; CY <- set if error
;
sint_bin:
mov cx, 10 ; multiply constant
xor ax, ax ; initialize accumulator
xor dx, dx
xor bh, bh ; sign flag FALSE
cmp BYTE PTR [si], '-' ; signed?
jne get_schar ; no
inc si ; next char
inc bh ; set signed flag
get_schar: mov bl, [si] ; get input character
inc si ; bump ptr
or bl, bl ; end of string?
jz set_sign
cmp bl, '0' ; check valid
jb err_ret
cmp bl, '9'
ja err_ret
sub bl, '0' ; convert digit to binary
mul cx ; decimal shift left
add al, bl ; new digit
adc ah, 0 ; propogate carry
js err_ret ; quit if sign overflow
adc dx, 0
jnz err_ret ; quit if overflow
jmp get_schar
set_sign: or bh, bh ; sign flag on
jz done ; no - return
neg ax ; else complement
done: clc ; no error
ret
err_ret: stc ; CY is error flag
ret

; Function 1 / ASCII UNSIGNED INT to BINARY conversion
; AH = 1
; DS:SI -> Null terminated string of digits
; Returns:
; AX <- 16-bit unsigned integer
; CY <- set if error
;
uint_bin:
mov cx, 10 ; multiply constant
xor ax, ax ; initialize accumulator
xor dx, dx
get_uchar: mov bl, [si] ; get input character
inc si ; bump ptr
or bl, bl ; end of string?
jz done ; yes - return
cmp bl, '0' ; check valid
jb err_ret
cmp bl, '9'
ja err_ret
sub bl, '0' ; convert digit to binary
mul cx ; decimal shift left
add al, bl ; new digit
adc ah, 0 ; propogate carry
adc dx, 0
jnz err_ret ; quit if overflow
jmp get_uchar

; Function 2 / ASCII HEX to BINARY conversion
; AH = 2
; DS:SI -> Null terminated string of digits
; Returns:
; AX <- 16-bit unsigned
; CY <- set if error
;
hex_bin:
xor dx, dx ; init accumulator
get_hchar: lodsb ; get character
or al, al ; last char?
jnz test_hchars
mov ax, dx
ret ; CY cleared by OR
test_hchars: cmp al, '0' ; check valid digit
jb err_ret
cmp al, '9'
jbe got_valid
or al, 20h ; must be alpha - force lower
cmp al, 'a' ; check valid char
jb err_ret
cmp al, 'f'
ja err_ret
sub al, 27h ; adjust range
got_valid: sub al, '0' ; convert digit to binary
cmp dx, 0FFFh ; test overflow
ja err_ret
shl dx, 4 ; hex shift left
add dl, al ; insert new digit
jmp get_hchar

; Function 3 / BINARY to ASCII SIGNED INT conversion
; AH = 3
; DX -> 16-bit signed
; ES:DI -> Buffer for ascii string
; Returns:
; Null terminated ASCII string at ES:DI
;
bin_sint: test dh, 80h ; sign bit?
jz bin_uint ; no - treat as unsigned
mov al, '-' ; else write sign
stosb
neg dx ; and complement
jmp bin_uint

div_tab DW 10000
DW 1000
DW 100
DW 10
DW 1

; Function 4 / BINARY to ASCII UNSIGNED INT conversion
; AH = 4
; DX -> 16-bit unsigned
; ES:DI -> Buffer for ascii string
; Returns:
; Null terminated ASCII string at ES:DI
;
bin_uint: mov si, OFFSET div_tab ; index
xor bx, bx ; bh is zero suppress flag
mov ax, dx ; value
u_loop: cmp WORD PTR CS:[si], 1 ; last divisor?
je u_out ; yes - output last digit
xor dx, dx ; high order zero
div WORD PTR CS:[si] ; DX:AX/10^n
or ax, bx ; quotient == 0 || ! suppress?
jz u_loop
mov bh, 1 ; turn off zero suppress flag
add al, '0' ; quotient always single digit
stosb
mov ax, dx ; restore AX with remainder
inc si
inc si ; next divisor
jmp u_loop
u_out: add al, '0' ; last digit
stosb
xor al, al ; ASCII null
stosb
ret

; Function 5 / BINARY to ASCII HEX conversion
; AH = 5
; DX -> 16-bit unsigned
; ES:DI -> Buffer for ascii
; Returns:
; Null terminated 4 character ASCII string at ES:DI
;
bin_hex: mov al, dh ; high order byte
shr al, 4 ; high nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h1
add al, 7 ; ajust alpha
bin_h1: stosb
mov al, dh ; high order byte
and al, 0Fh ; low nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h2
add al, 7 ; ajust alpha
bin_h2: stosb
mov al, dl ; low order byte
shr al, 4 ; high nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h3
add al, 7 ; ajust alpha
bin_h3: stosb
mov al, dl ; low order byte
and al, 0Fh ; low nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h4
add al, 7 ; ajust alpha
bin_h4: stosb
xor al, al
stosb ; ASCII null
ret

shlib_code ENDP
ENDSEG SHLIB

PAGE
;
; This section contains the main code and data. We come here initially
; in Real Address Mode, perform necessary setup, and enter Protected
; Virtual Address Mode. The data segment has combine type STACK
; so that the linker will initialize SS:SP.
;
MSEG DSC,RD_WR,0,STACK

no_pm_msg DB '*** Unable to enter protected mode ***$'
blank_line DB 80 DUP (' ')
DB 0
msg DB 'Testing',0

ALIGN 2 ; force stack to word bound
DW 100 DUP (?) ; stack
ENDSEG DSC

SSEG INIT_TSS,TSS,0
TSS_BLOCK <> ; uninitialized
ENDSEG INIT_TSS

MSEG INIT,EXONLY,0
ASSUME CS:INIT, DS:DSC

adjust_addr PROC NEAR
; This subroutine marches through a descriptor table to fixup 16-bit
; segment addresses to full 24-bit physical addresses. Since the
; segment fixups were done by the DOS linker in Real Address Mode,
; all we need to do is multiply by 16. We assume the high order 8
; bits are zero, i.e., all addresses are in the first 1Mb.
; Called with ES:0 pointing to table, CX is number of entries.
xor bx, bx ; initial offset
l1: mov al, ES:[bx].access ; get access rights byte
test al, 10h ; is descriptor a segment?
jnz got_seg ; yes
and al, 0Fh ; extract type
cmp al, 3 ; gate?
ja update_next ; yes - skip segment adjust
got_seg: mov ax, ES:[bx].phys_addr_lo; get segment
mov dx, 16
mul dx ; convert to phys addr
mov ES:[bx].phys_addr_lo, ax; store
mov ES:[bx].phys_addr_hi, dl; 24 bits
update_next: add bx, 8 ; incr to next descrip
loop l1
ret
adjust_addr ENDP

start:
mov ax, DSC ; set up DS
mov ds, ax
sti

; When DOS created the prototype descriptors, it placed segment addresses
; in the physical address portion of the segment descriptors. We must
; fix up all descirptor tables which contain segment descriptors.
mov ax, T2LDT
mov es, ax ; point to proto LDT
mov cx, t2ldt_limit ; get limit
inc cx ; bump to size in bytes
shr cx, 3 ; convert to # entries
call adjust_addr

mov ax, GDT
mov es, ax ; point to proto GDT
mov cx, gdt_limit
inc cx
shr cx, 3 ; gdt entries
call adjust_addr

; Now we ask the BIOS to place us in protected mode. The BIOS requires
; the first 7 descriptors of the GDT to be setup as we have done. This
; gives it enough information to load GDTR and IDTR and setup a new
; code and data segment for the calling routine. The BIOS will also
; program the 8259A to our requested interrupt vectors. Additionally, it
; sets up the internal AT hardware to allow addresses > 1Mb to go out
; over the bus (frees A20 line).
xor si, si ; ES:SI -> proto GDT
mov bh, 20h ; int level 1 start
mov bl, 28h ; int level 2 start
mov ah, 89h ; enter PM request
mov cx, 0FFFFh ; idle here to ensure all
loop $ ; DOS keybd ints processed
int 15h ; BIOS call
jnc vm ; successful if no CY bit

mov ah, 9 ; no - print message
mov dx, OFFSET no_pm_msg
int 21h
mov ax, 4C01h ; failure
int 21h ; exit

;;; NOW IN PROCTED MODE -- INTS DISABLED

vm:
mov bp, sp ; setup registers
mov ax, ds
mov es, ax
mov ax, OFFSET setup_tss ; active task
ltr ax

pm_init_done:
mov ah, MBIOS_BELL ; bell
int 30h
call idle

mov ah, MBIOS_CLS ; cls
int 30h

; Enable ints
xor al, al ; no ints masked
out MASTER+1, al
out SLAVE+1, al
sti

; Print number of ticks so far
print_ticks:
mov ah, MBIOS_CLS ; clear screen
int 30h
mov dx, 0010h
mov ah, MBIOS_WR_CRSR
int 30h
mov ax, OFFSET bio_dat
mov es, ax
cli
mov dx, WORD PTR ES:tick_ctr ; get tick counter
mov ax, WORD PTR ES:tick_ctr[2]
sti
push dx
call pr_hex_word ; print high order
pop ax
call pr_hex_word ; print low order
call idle ; pause

CALL_EX task2_tss,0 ; invoke task 2

call idle
call idle
mov ah, MBIOS_BELL ; bell
int 30h
jmp print_ticks ; loop forever


idle PROC NEAR
push bx
push cx
mov bx, 10
iloop: mov cx, 0FFFFh
loop $
dec bx
jnz iloop
pop cx
pop bx
ret
idle ENDP

pr_hex_word PROC
; Print word in AX
push bp
mov bp, sp
sub sp, 10 ; space for string on stack
push ds
pop es ; es = ds
lea di, [bp-10] ; destination
mov dx, ax ; value
mov ah, LIB_BIN_HEX ; function
CALL_EX share_gate,0 ; shared code
lea si, [bp-10] ; hex string ptr
mov ah, MBIOS_WR_STRING ; function
int 30h ; print string
mov sp, bp
pop bp
ret
pr_hex_word ENDP
ENDSEG INIT

PAGE
; Finally, we have a small second task, which will alternate execution
; with the initial task. It runs at privelege level 3, which means it
; has access only to its code segment, data segment, the shared library
; gate and INT 30h.

SSEG T2LDT,LDT,0
; All memory segments for this task reside in a local descriptor table.
DSCRP task2_cs,CODE2 ; CS for 2nd task
DSCRP task2_dsc,DSC2 ; DS/SS for 2nd task
DSCRP task2_stk0,STK2_0 ; Level 0 stack for OS calls
ENDSEG T2LDT

SSEG TASK2,TSS,0
DW 0 ; back link
DW STK2_0_limit + 1 ; SP0
LDT_SEL task2_stk0,0 ; SS0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW top ; initial IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW stack2 ; SP
DW stack2 ; BP
DW 2 DUP (0) ; SI/DI
LDT_SEL task2_dsc,3 ; ES - segments all in LDT
LDT_SEL task2_cs,3 ; CS
LDT_SEL task2_dsc,3 ; SS
LDT_SEL task2_dsc,3 ; DS
GDT_SEL task2_ldt,0 ; LDT selector
ENDSEG TASK2

MSEG STK2_0,RD_WR,0
DW 128 DUP (?) ; stack for level 0 execution
ENDSEG STK2_0

MSEG DSC2,RD_WR,3
; Data and stack segment for 2nd task
t2_msg DB "Task 2 running",0

ALIGN 2 ; stack on word boundary
DW 128 DUP (?)
stack2 LABEL WORD

ENDSEG DSC2

MSEG CODE2,EXONLY,3
ASSUME CS:CODE2, DS:DSC2
top: ; task starts here first time
mov dx, 0032h
mov ah, MBIOS_WR_CRSR
int 30h ; cursor to "safe" location
mov si, OFFSET t2_msg
mov ah, MBIOS_WR_STRING
int 30h ; print message
iret ; return to previous task
jmp top ; when task invoked again,
; CS:IP points here (after IRET)
ENDSEG CODE2

END start


  3 Responses to “Category : Assembly Language Source Code
Archive   : ATPROTMD.ZIP
Filename : PM_AT.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/