! runtime.S - Runtime module for mgl compiler ! ! Copyright (C) 1997-2003 Gero Kuhlmann ! ! This program is free software; you can redistribute it and/or modify ! it under the terms of the GNU General Public License as published by ! the Free Software Foundation; either version 2 of the License, or ! any later version. ! ! This program is distributed in the hope that it will be useful, ! but WITHOUT ANY WARRANTY; without even the implied warranty of ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! GNU General Public License for more details. ! ! You should have received a copy of the GNU General Public License ! along with this program; if not, write to the Free Software ! Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ! ! $Id: runtime.S,v 1.4 2003/01/25 23:29:44 gkminix Exp $ #ifndef ASM_DEBUG #undef ASM_DEBUG #endif #include "runtime.inc" #include ! !************************************************************************** ! ! Miscellaneous defines: ! ARGMAX equ 2 ! maximum number of ANSI arguments INTBUFLEN equ 10 ! size of temporary integer buffer MAXLINES equ 25 ! default number of screen lines MEMEND_SEG equ $A000 ! end segment of base RAM BIOS_SEG equ $0040 ! BIOS data segment BIOS_FREEMEM equ $0013 ! number of free base memory kb INT_0_OFS equ 0 * 4 ! offset to interrupt vector 0 INT_4_OFS equ 4 * 4 ! offset to interrupt vector 4 INT_5_OFS equ 5 * 4 ! offset to interrupt vector 5 ! !************************************************************************** ! ! Definitions for accessing the bootrom PXE interface: ! ! Definitions for getting the PXENV structure pointer ! PXENV_INT equ $1A ! interrupt to call PXENV_ENTRY_ID equ $5650 ! ID to set before calling int PXENV_EXIT_ID equ $564E ! ID returned by int ! ! Offsets into +PXENV structure ! PXENV_VERSION equ $0006 ! offset to structure version PXENV_PXEPTR equ $0028 ! offset to !PXE far ptr API_VERSION equ $0201 ! version number of +PXENV structure ! ! Offsets into !PXE structure ! PXE_ENTRY equ $0010 ! offset to PXE API entry point PXE_UNDI_DS equ $0028 ! offset to UNDI data seg descriptor PXE_UNDI_CS equ $0030 ! offset to UNDI code seg descriptor DESC_BASE_LOW equ $0002 ! offset to segment base address (LSW) DESC_BASE_MID equ $0004 ! offset to segment base address (ISB) DESC_BASE_HIGH equ $0007 ! offset to segment base address (MSB) DESC_SIZE_LOW equ $0000 ! offset to segment limit (LSW) DESC_SIZE_HIGH equ $0006 ! offset to segment limit (MSB) ! ! PXE API function codes ! GEN_UNLOAD_STACK equ $0070 ! unload the bootrom kernel GEN_STOP_BASE equ $0076 ! stop bootrom services UNDI_CLEANUP equ $0002 ! unload UNDI from memory UNDI_STOP_UNDI equ $0015 ! stop UNDI services ! !************************************************************************** ! ! Definitions for supporting old bootrom service interrupt: ! SERVICE_INT equ $78 ! Bootrom services interrupt SERVICE_MAGIC equ $474B ! Magic ID ! !************************************************************************** ! ! Definitions for loading an operating system from a disk: ! HD_BASE equ $80 ! base number for all harddisks FD_BASE equ $00 ! base number for all floppy disks BOOT_BLOCK equ $7C00 ! offset to boot block after loading BOOT_SIG equ $AA55 ! boot block signature BOOT_SIG_OFS equ $01FE ! offset to boot block signature PART_BUF_OFS equ $0200 ! offset to partition table buffer PART_OFS equ $03BE ! offset to partition table PART_HEAD_OFS equ $0001 ! offset to head number in part entry PART_SECT_OFS equ $0002 ! offset to sector number in part entry PART_TYPE_OFS equ $0004 ! offset to partition type indicator SECTSIZE equ 512 ! size of one sector ENTRY_LEN equ $0010 ! size of one partition table entry ENTRY_NUM equ 4 ! maximum number of entries in part tab EXT_PART_TYPE equ 5 ! type ID for extended partition ! !************************************************************************** ! ! Defines for the ANSI escape code simulator: ! CHR_BELL equ $07 ! bell character CHR_BS equ $08 ! backspace character CHR_TAB equ $09 ! tab character CHR_LF equ $0A ! linefeed character CHR_CR equ $0D ! carriage return character CHR_ESC equ $1B ! escape character CHR_SPACE equ $20 ! space character CHR_COLON equ $21 ! colon character CHR_MINUS equ $2D ! minus character CHR_ZERO equ $30 ! zero character CHR_NINE equ $39 ! nine character CHR_EQUAL equ $3D ! equal sign CHR_QUEST equ $3F ! question mark CHR_BRACKET equ $5B ! left bracket FLAG_NOWRAP equ %00000001 ! flag indicating not to wrap FLAG_INVERS equ %00000010 ! flag for invers video FLAG_BLINK equ %00000100 ! flag for blinking FLAG_INTENSE equ %00001000 ! flag for high intensity FLAG_NORMAL equ %11110001 ! mask to set flags to normal mode NORMAL_STATE equ 0 ! normal display state ESC_STATE equ 1 ! found escape in input stream BRACKET_STATE equ 2 ! found bracket in input stream NUMBER_STATE equ 3 ! found numbers in input stream ! !************************************************************************** ! ! Start the code segment. Everything is in here, we have no seperate ! data segment for the runtime module. ! .text .org 0 jmp start ! !************************************************************************** ! ! This is the place where the code generator puts some required ! size values. ! .align DATAOFS execute: .word 0 ! start address of compiled code codesize: .word 0 ! size of compiled code constsize: .word 0 ! size of constant data datasize: .word 0 ! size of variable data debugofs: .word 0 ! offset to debugging buffer jmp near dispatch ! jump to runtime dispatcher ! !************************************************************************** ! ! Runtime startup code. Since this code can be run by the bootrom or ! directly as a DOS COM program, we have to check for this and reset ! the segment registers so that the runtime code is always at offset ! zero. Also, there is no load record and BOOTP information with a ! DOS program. ! start: call start1 start1: pop dx ! if the runtime code didnt start cmp dx,#start1 ! at offset zero, we are running as je nodos ! a DOS program. sub dx,#start1 mov cl,#4 shr dx,cl ! recalculate the code segment so mov ax,cs ! that the runtime module starts add dx,ax ! at offset zero mov ds,dx push dx ! simulate a call from the bootrom so push #dosret ! that we can return to DOS with a far push dx ! call push #common ! reset CS and IP retf dosret: mov ax,#$4C00 ! terminate to DOS. int $21 ! We are not running as a DOS program, so we can save the bootrom values ! from the stack. nodos: mov dx,ds mov ax,cs ! set DS mov ds,ax mov [oldES],es mov [oldDS],dx ! save old register values in case mov [oldBP],bp ! we have to return to the boot rom mov [oldSI],si mov [oldDI],di mov bp,sp mov ax,[bp + 4] mov [header + 0],ax ! load the address of the boot header mov ax,[bp + 6] mov [header + 2],ax mov ax,[bp + 8] mov [bootp + 0],ax ! load the address of the bootp block mov ax,[bp + 10] mov [bootp + 2],ax inc byte ptr [userom] ! indicate that we were started by rom ! Tell the user who we are and that we started running mov si,#sigmsg call prnstr ! Check if the boot image header is correct. les bx,[header] mov si,#bmerr ! prepare for correct error message seg es mov ax,BOOT_HD_MAGIC + 0[bx] cmp ax,[bmagic + 0] ! compare boot rom magic numbers jne doerr seg es mov ax,BOOT_HD_MAGIC + 2[bx] cmp ax,[bmagic + 2] jne doerr mov si,#vmerr ! prepare for correct error message seg es mov al,BOOT_HD_LENGTH[bx] mov cl,#4 shr al,cl and al,#$0F cmp al,#VENDOR_SIZE ! check vendor ID size jne doerr xor di,di dovmag: mov al,vmagic[di] ! check vendor ID or al,al jz chkpxe ! vendor ID ok, continue seg es cmp al,BOOT_HD_VENDOR[bx + di] jne doerr inc di jmp dovmag doerr: call prnstr ! in case of error return to the xor bx,bx ! boot rom jmp near doret ! Next check that the bootrom supports the PXE or service interrupt. chkpxe: call findpxe ! first check for PXE support jnc dobotp mov si,#interr ! prepare for correct error message xor ax,ax mov es,ax seg es mov ax,word ptr [SERVICE_INT * 4 + 0] seg es or ax,word ptr [SERVICE_INT * 4 + 2] jz doerr ! error if no interrupt found xor ah,ah int SERVICE_INT ! get service code cmp ax,#SERVICE_MAGIC jne doerr ! wrong magic ID ! Check that the BOOTP record is correct and determine its size dobotp: cld xor dx,dx les di,[bootp] mov ax,es or ax,di ! do we have a bootp pointer at all jz setbtp dobot1: seg es mov al,BOOTP_OP[di] ! op code must indicate reply cmp al,#BOOTP_REPLY jne doerr1 ! it isnt mov bx,di add di,#BOOTP_VEND mov si,#pmagic ! compare vendor ID mov cx,#BOOTP_MAGIC_LEN repe cmpsb jnz doerr1 ! vendor ID not found xor cx,cx dobot4: seg es mov al,[di] ! get next tag inc di cmp al,#BOOTP_RFC_NOP ! handle NOP tag jnz dobot5 inc cx cmp cx,#16 ! more than 16 NOP tags is VERY unusual jb dobot4 ! so the bootp record maybe broken doerr1: mov si,#btperr jmp doerr dobot5: cmp al,#BOOTP_RFC_END ! handle END tag jnz dobot6 mov dx,di sub dx,bx ! compute length of bootp record cmp dx,#BOOTP_SIZE jae setbtp mov dx,#BOOTP_SIZE ! use minimum size jmp setbtp dobot6: seg es ! handle all other tags mov cl,[di] xor ch,ch inc di add di,cx ! jump to next tag xor cx,cx ! reset NOP counter jmp dobot4 ! proceed with next tag setbtp: mov [btplen],dx ! set length of bootp record ! Get the screen size from the BIOS and setup some global variables common: mov ah,#$0F ! determine current screen mode int $10 mov [scncolumns],ah ! set number of columns on screen mov [scnpage],bh ! set screen page mov al,bh mov ah,#$05 ! select screen page int $10 ! Now setup the data and stack segments for the image, relocate the interrupt ! vectors and call the program. mov dx,cs mov bx,[codesize] mov cl,#4 shr bx,cl ! compute start of data segment add bx,dx mov [newDS],bx cld ! the mgl program relies on the cli ! direction flag to be cleared mov [oldSS],ss mov [oldSP],sp ! save old stack pointer mov ss,bx mov sp,#$FEEE ! setup new stack xor ax,ax mov es,ax seg es mov ax,[INT_0_OFS + 0] ! save old interrupts mov [oldint0 + 0],ax seg es mov ax,[INT_0_OFS + 2] mov [oldint0 + 2],ax seg es mov ax,[INT_4_OFS + 0] mov [oldint4 + 0],ax seg es mov ax,[INT_4_OFS + 2] mov [oldint4 + 2],ax seg es mov ax,[INT_5_OFS + 0] mov [oldint5 + 0],ax seg es mov ax,[INT_5_OFS + 2] mov [oldint5 + 2],ax mov ax,cs seg es mov [INT_0_OFS + 2],ax seg es mov [INT_4_OFS + 2],ax ! set new interrupt vectors seg es mov [INT_5_OFS + 2],ax mov ax,#int0 seg es mov [INT_0_OFS + 0],ax mov ax,#int4 seg es mov [INT_4_OFS + 0],ax mov ax,#int5 seg es mov [INT_5_OFS + 0],ax sti xor ax,ax mov es,bx mov di,[constsize] ! clear the variable data area mov cx,[datasize] inc cx shr cx,#1 rep stosw ! Copy the server and host names from the BOOTP record into the programs ! data segment mov al,[userom] or al,al ! no hostname for DOS program jz doimg lds si,[bootp] mov ax,ds ! check if we have a BOOTP record or ax,si jz doimg add si,#BOOTP_SNAME mov di,#ADDR_SERVERNAME seg cs add di,[constsize] mov cx,#BOOTP_MAX_SNAME ! copy server name rep movsb xor al,al ! terminate with a zero stosb push bx push es mov ax,#BOOTP_RFC_HOST call fndtag ! find hostname tag mov ax,es ! this does not need DS to be set pop es pop bx jc doimg mov si,di mov di,#ADDR_HOSTNAME seg cs add di,[constsize] mov ds,ax lodsb ! get length of string xor ah,ah cmp ax,#MAX_STR_LEN jbe cphost mov ax,#MAX_STR_LEN ! dont let the string buffer overflow cphost: mov cx,ax rep movsb ! actually copy the host name xor al,al ! terminate with a zero stosb ! Now call the compiled program doimg: mov ds,bx ! setup new data segment seg cs call word ptr [execute] ! call the compiled program xor bx,bx ! dont call us again ! Here the program returns. We simply restore the registers and return ! to the bootrom. imgret: mov ax,cs mov ds,ax ! restore old data segment call intrestore ! restore old interrupts cli mov ss,[oldSS] mov sp,[oldSP] ! restore old stack doret: sti mov si,[oldSI] ! restore old registers mov di,[oldDI] mov bp,[oldBP] mov es,[oldES] mov ds,[oldDS] mov ax,bx ! set return value retf ! return to bootrom ! !************************************************************************** ! ! Find a !PXE entry structure ! Input: none ! Output: Carry flag set if structure not found ! Registers changed: AX, BX, CX, DX, SI, DI, ES ! findpxe: ! First find the +PXENV entry point structure, check that its correct and ! get the pointer to the !PXE structure out of it. stc mov ax,#PXENV_ENTRY_ID int PXENV_INT ! get address of +PXENV structure jc findp8 cmp ax,#PXENV_EXIT_ID ! check for correct return value and jne findp8 ! structure version seg es cmp word ptr [bx + PXENV_VERSION],#API_VERSION jne findp8 seg es les bx,[bx + PXENV_PXEPTR] ! get pointer to !PXE structure ! Check that the signature of the !PXE structure is correct. If it is, ! save its address. cld mov di,bx mov si,#pxesig ! check that the !PXE structure starts mov cx,#4 ! with the correct signature repe cmpsb je findp1 findp8: stc ret findp1: mov word ptr [pxestruct + 0],bx mov word ptr [pxestruct + 2],es ! Compute the lowest segment used by the UNDI driver. The PXE specification ! says that the data segment has to come below the text segment, but some ! netboot bootrom versions dont conform to this rule. mov cl,#4 seg es mov dx,word ptr [bx + PXE_UNDI_DS + DESC_BASE_LOW] shr dx,cl seg es mov al,byte ptr [bx + PXE_UNDI_DS + DESC_BASE_MID] seg es mov ah,byte ptr [bx + PXE_UNDI_DS + DESC_BASE_HIGH] ror ax,cl and ax,#$F000 or dx,ax push dx seg es mov dx,word ptr [bx + PXE_UNDI_CS + DESC_BASE_LOW] shr dx,cl seg es mov al,byte ptr [bx + PXE_UNDI_CS + DESC_BASE_MID] seg es mov ah,byte ptr [bx + PXE_UNDI_CS + DESC_BASE_HIGH] ror ax,cl and ax,#$F000 or dx,ax pop ax cmp ax,dx jb findp2 mov ax,dx findp2: cmp ax,#MEMEND_SEG jb findp3 mov ax,#MEMEND_SEG findp3: mov [drvseg],ax ! Compute the total size occupied by the UNDI driver and determine the ! UNDI end segment seg es mov dx,word ptr [bx + PXE_UNDI_DS + DESC_SIZE_LOW] add dx,#$000F shr dx,cl seg es mov ch,byte ptr [bx + PXE_UNDI_DS + DESC_SIZE_HIGH] shl ch,cl or dh,ch seg es mov ax,word ptr [bx + PXE_UNDI_CS + DESC_SIZE_LOW] add ax,#$000F shr ax,cl seg es mov ch,byte ptr [bx + PXE_UNDI_CS + DESC_SIZE_HIGH] shl ch,cl or ah,ch add ax,dx add ax,[drvseg] cmp ax,#MEMEND_SEG jb findp4 mov ax,#MEMEND_SEG findp4: mov [endseg],ax ! Finally save the address of the PXE API 16 bit entry point seg es les bx,[bx + PXE_ENTRY] mov word ptr [pxentry + 0],bx mov word ptr [pxentry + 2],es clc ! return without error ret ! !************************************************************************** ! ! Restore all interrupt vectors which have been redirected. ! Input: none ! Output: none ! Registers changed: AX, ES ! intrestore: cli xor ax,ax mov es,ax mov ax,[oldint0 + 0] seg es mov [INT_0_OFS + 0],ax mov ax,[oldint0 + 2] seg es mov [INT_0_OFS + 2],ax mov ax,[oldint4 + 0] seg es mov [INT_4_OFS + 0],ax mov ax,[oldint4 + 2] seg es mov [INT_4_OFS + 2],ax mov ax,[oldint5 + 0] seg es mov [INT_5_OFS + 0],ax mov ax,[oldint5 + 2] seg es mov [INT_5_OFS + 2],ax sti ret ! !************************************************************************** ! ! Handle runtime errors ! int0: mov si,#i0err jmp inter0 int4: mov si,#i4err jmp inter0 int5: mov si,#i5err inter0: sti mov ax,cs mov ds,ax ! restore data segment mov es,ax push si mov si,#rterr ! print error message call prnstr pop si call prnstr cld mov si,[debugofs] or si,si ! check if we have a debugging info jz inter8 ! table pop bx ! get faulty address from stack xor cx,cx push ds ! scan through debugging table to mov ds,[newDS] ! find the address inter1: lodsw or ax,ax ! end of table reached jz inter7 cmp ax,bx ! check for first entry with an addr ja inter7 ! higher than the faulty address lodsw mov cx,ax ! get last address jmp inter1 inter7: pop ds or cx,cx ! no line number found? jz inter8 mov si,#linerr call prnstr call prnnum ! print the line number inter8: mov si,#crlf call prnstr mov si,#keyerr ! print "keypress" message call prnstr mov ah,#$01 int $16 jz inter9 xor ax,ax ! clear keyboard buffer int $16 inter9: xor ax,ax int $16 ! wait for key press xor bx,bx ! indicate termination jmp near imgret ! terminate the program ! !************************************************************************** ! ! Print a line number ! Input: CX - line number ! Output: none ! Registers changed: AX, BX, DX, SI ! prnnum: mov si,#intbuf + INTBUFLEN - 1 mov byte ptr [si],#0 mov ax,cx mov bx,#10 prnn1: xor dx,dx div bx add dl,#CHR_ZERO dec si mov [si],dl or ax,ax jnz prnn1 ! Fall through to prnstr !!!! ! !************************************************************************** ! ! Print a string onto the console. This is only used for error outputs. ! Input: DS:SI - pointer to zero-terminated string ! Output: none ! Registers changed: AL, BX ! prnstr: push si cld prns1: lodsb ! loop over all characters of or al,al ! string jz prns2 mov ah,#$0E ! print it mov bl,#$07 xor bh,bh int $10 jmp prns1 prns2: pop si ret ! !************************************************************************** ! ! String and constants definitions for the loader part of the runtime ! module. ! ! Startup signature sigmsg: .byte $0D, $0A .ascii "Menu Net Boot Image Loader " .ascii VERSION .byte $0D, $0A .ascii COPYRIGHT .byte $0D, $0A crlf: .byte $0D, $0A .byte 0 ! Signature of !PXE structure pxesig: .ascii "!PXE" ! Magic numbers for boot record and bootp entry bmagic: .long BOOT_MAGIC ! boot image magic number vmagic: .ascii VENDOR_MAGIC ! vendor magic ID .byte 0 ! end of vendor magic ID pmagic: .byte BOOTP_MAGIC_RFC ! bootp magic ID for RFC 1048 ! Error messages interr: .ascii "Bootrom does not support required services" .byte $0D, $0A .byte 0 bmerr: .ascii "Invalid boot header magic number" .byte $0D, $0A .byte 0 vmerr: .ascii "Invalid vendor magic ID" .byte $0D, $0A .byte 0 btperr: .ascii "BOOTP record invalid" .byte $0D, $0A .byte 0 rterr: .asciz "Runtime error: " i0err: .asciz "Division by zero" i4err: .asciz "Integer overflow" i5err: .asciz "Scalar value out of bounds" linerr: .asciz " at line " keyerr: .asciz "Press any key to terminate the program" ! !************************************************************************** ! ! Variable definitions for runtime module loader ! header: .long 0 ! pointer to boot header from boot rom bootp: .long 0 ! pointer to bootp block from boot rom oldDS: .word 0 ! old DS from boot rom oldES: .word 0 ! old ES from boot rom oldBP: .word 0 ! old BP from boot rom oldSI: .word 0 ! old SI from boot rom oldDI: .word 0 ! old DI from boot rom oldSS: .word 0 ! old SS from boot rom oldSP: .word 0 ! old SP from boot rom newDS: .word 0 ! new data segment btplen: .word 0 ! length of bootp record oldint0: .long 0 ! old interrupt 0 (division by zero) oldint4: .long 0 ! old interrupt 4 (integer overflow) oldint5: .long 0 ! old interrupt 5 (bound check) ! !************************************************************************** ! ! Start of actual runtime module. It provides all the routines ! necessary for the compiled mgl program, including an ANSI ! control escape simulator. ! !************************************************************************** ! ! Function table. The order of the functions has to be the same as ! in the relevant C header files. ! ftab1: .word sprint ! print a string .word iprint ! print an integer number .word cprint ! print a character .word sget ! read a string .word iget ! read an integer number .word cget ! read a character .word getsel ! read a char for select .word load ! jump back to bootrom MAX_FUNC1 equ $08 ftab2: .word cls ! clear screen .word gotoxy ! goto with cursor to a screen position .word getbootp ! get a string from bootp record .word putbootp ! put a string into bootp record .word strlen ! determine string length .word strsub ! get substring .word crypt ! crypt a string MAX_FUNC2 equ $06 ! !************************************************************************** ! ! The function dispatcher gets a function code in AH, and then calls ! an appropriate handler. ! Input: AH - function code ! other depend on called function ! Output: depends on called function ! Registers changed: AX, others depend on function called ! dispatch: push bx mov al,ah xor ah,ah ! check for type of internal function cmp ax,#CMD_FIRSTINT jae disp2 cmp ax,#MAX_FUNC1 ! not a valid function jae nofunc mov bx,#ftab1 jmp disp1 disp2: sub ax,#CMD_FIRSTINT cmp ax,#MAX_FUNC2 ! not a valid function jae nofunc mov bx,#ftab2 disp1: shl ax,#1 add bx,ax seg cs mov ax,word ptr [bx] pop bx ! call function jmp ax nofunc: pop bx ! not a valid function, simply return ret ! !************************************************************************** ! ! Print a character ! Input: CL - character to be printed ! Output: none ! Registers changed: AX ! cprint: mov al,cl jmp near outchar ! print character ! !************************************************************************** ! ! Print a zero-terminated string ! Input: DS:SI - pointer to string ! Output: none ! Registers changed: AX, SI ! sprint: cld sprnt1: lodsb or al,al jz sprnt9 call outchar ! simply print each character jmp sprnt1 ! in turn sprnt9: ret ! !************************************************************************** ! ! Print a signed integer number ! Input: CX - number to be printed ! Output: none ! Registers changed: AX, BX, CX, DX, SI ! iprint: or cx,cx jns iprnt1 mov al,#CHR_MINUS ! print a minus sign call outchar neg cx ! make number positive iprnt1: push ds mov ax,cs mov ds,ax mov si,#intbuf+INTBUFLEN-2 ! setup pointer to end of conversion mov byte ptr [si + 1],#0 ! buffer and terminate it with a zero mov ax,cx mov bx,#10 iprnt2: xor dx,dx div bx ! divide number by 10 add dl,#CHR_ZERO ! convert remainder into ascii mov byte ptr [si],dl dec si ! save it into the buffer or ax,ax jnz iprnt2 ! continue until quotient is zero inc si call sprint ! actually print the number pop ds ret ! !************************************************************************** ! ! Get a character from the keyboard. If no key has been pressed during ! the timeout period, return 0xff. If the timeout value is zero, no ! timeout is set. The character will NOT be echoed to the screen. ! Input: DX - timeout in seconds ! Output: AL - read character ! Registers changed: AX ! cget: push bx push cx push dx or dx,dx ! check if we have a timeout jz cget3 mov ax,dx mov bx,#18 ! convert timeout into timer ticks mul bx seg cs mov word ptr [cntend + 0],ax seg cs mov word ptr [cntend + 2],dx xor ah,ah int $1A ! get system time counter value seg cs ! add timeout to counter value add word ptr [cntend + 0],dx seg cs adc word ptr [cntend + 2],cx cget2: mov ah,#$01 int $16 ! check if key pressed jnz cget3 xor ah,ah int $1A ! check system counter if end value seg cs ! reached sub dx,word ptr [cntend + 0] seg cs sbb cx,word ptr [cntend + 2] jc cget2 mov ax,#$FF ! timeout reached jmp cget9 cget3: xor ah,ah ! get the character using the BIOS int $16 cget9: pop dx ! return to caller pop cx pop bx ret ! !************************************************************************** ! ! Get a string from the keyboard. ! Input: ES:DI - pointer to destination buffer ! CX - size of destination buffer ! DX - timeout in seconds ! Output: none ! Registers changed: AX, BX, CX, SI, DI ! sget: push ds mov ax,cs mov ds,ax ! set correct data segment jcxz sget8 ! check if buffer is large enough cld mov si,di ! preserve buffer start sget1: call cget ! get a character from the keyboard cmp al,#$FF ! check for timeout je sget8 cmp al,#CHR_CR ! check for enter key je sget8 cmp al,#CHR_BS ! check for backspace je sget2 cmp al,#CHR_SPACE ! we dont accept any other control jb sget1 ! keys jcxz sget1 ! end of buffer reached stosb ! save character dec cx call outchar ! and print it jmp sget1 sget2: cmp di,si ! handle backspace jbe sget1 ! cannot backup below buffer start dec di inc cx call outchar ! print backspace mov al,#CHR_SPACE ! print space call outchar mov al,#CHR_BS ! print another backspace call outchar jmp sget1 sget8: seg es mov byte ptr [di],#0 ! terminate string with a zero pop ds ret ! !************************************************************************** ! ! Get an integer value. ! Input: DX - timeout in seconds ! Output: AX - read integer value ! Registers changed: AX, BX, CX ! iget: push ds mov ax,cs mov ds,ax xor cx,cx ! CL is character counter, CH is sign xor bx,bx ! BX gets the return value iget1: call cget ! get a character cmp al,#$FF ! timeout? je iget14 cmp al,#CHR_CR ! return key? je iget14 cmp al,#CHR_BS ! backspace? je iget4 cmp al,#CHR_MINUS ! sign key? je iget7 cmp al,#CHR_ZERO jb iget1 ! dont accept anything else than cmp al,#CHR_NINE ! numbers ja iget1 jmp iget13 iget14: jmp iget8 ! Handle a number key. We add it directly to the result value. iget13: push dx push cx mov cx,ax mov ax,#10 mul bx ! multiply result by 10 sub cl,#CHR_ZERO ! convert character into number xor ch,ch add ax,cx ! add it to the result adc dx,#0 or dx,dx jnz iget2 ! check if the result gets too large or ax,ax js iget2 mov bx,ax ! move the new value into the result add cl,#CHR_ZERO ! register and print the number last mov al,cl ! entered call outchar pop cx iget11: inc cl ! increment character counter jmp iget3 iget2: pop cx iget3: pop dx iget12: jmp iget1 ! Handle backspace key iget4: or cl,cl jz iget12 ! no action if at beginning of number cmp cl,#1 ja iget5 or ch,ch ! check if we have a sign character jz iget5 xor ch,ch ! delete sign character jmp iget6 iget5: push dx xor dx,dx mov ax,bx mov bx,#10 ! divide the result by 10 to delete div bx ! the last character mov bx,ax pop dx iget6: mov al,#CHR_BS call outchar ! print backspace mov al,#CHR_SPACE call outchar ! print blank mov al,#CHR_BS call outchar ! print another backspace dec cl ! decrement character counter jmp iget12 ! Handle a sign character iget7: or cl,cl jnz iget12 ! only allowed in first position mov ch,al ! save sign character call outchar ! print it inc cl ! increment character counter jmp iget12 ! Thats it, return the result iget8: pop ds mov ax,bx ! return number cmp ch,#CHR_MINUS ! negative number? jne iget9 neg ax ! make it negative iget9: ret ! !************************************************************************** ! ! Read a character for select command. ! Input: DX - timeout in seconds ! Output: AL - character typed ! Registers changed: AX, BX, DX getsel: push ds mov ax,cs mov ds,ax mov al,#CHR_SPACE call outchar ! clear position where to enter the char mov bx,[curpos] push dx push bx mov dx,bx call putcursor ! replace the cursor pop bx pop dx gets1: call cget ! get a character cmp al,#$FF ! return immediately with a timeout je gets9 cmp al,#CHR_CR ! enter key is the same as timeout je gets8 cmp al,#CHR_ZERO jb gets1 ! dont accept anything else but cmp al,#CHR_NINE ! number keys ja gets1 call outchar ! print number push ax mov dx,bx call putcursor ! replace the cursor pop ax jmp gets9 gets8: mov al,#$FF gets9: pop ds ret ! !************************************************************************** ! ! Clear the screen ! Input: none ! Output: none ! Registers changed: AX ! cls: mov al,#CHR_ESC call outchar mov al,#CHR_BRACKET ! we simply use the ANSI sequence call outchar ! for clearing the screen mov al,#$4A jmp near outchar ! !************************************************************************** ! ! Put cursor to a specified position on the screen ! Input: 1. arg - column ! 2. arg - row ! Output: none ! Registers changed: AX, BX, CX, DX ! gotoxy: push bp mov bp,sp push ds mov ax,cs ! set correct data segment mov ds,ax mov ax,[bp + 4] ! get row number cmp ax,#MAXLINES jb gotox1 ! limit to MAXLINES mov ax,#MAXLINES gotox1: mov dh,al mov ax,[bp + 6] ! get column number cmp al,[scncolumns] jb gotox2 ! limit to max number of columns mov al,[scncolumns] gotox2: mov dl,al call putcursor ! move cursor to new position pop ds pop bp ret ! !************************************************************************** ! ! Determine the length of a string ! Input: 1. arg - string pointer (passed with constant attribute) ! Output: AX - string length ! Registers changed: AX, CX ! strlen: push bp mov bp,sp push di mov di,[bp + 4] ! get string pointer xor al,al mov cx,#MAX_STR_LEN + 1 cld repne scasb ! scan for terminating zero byte mov ax,#MAX_STR_LEN sub ax,cx ! compute length pop di pop bp ret ! !************************************************************************** ! ! Get a substring from a string argument ! Input: 1. arg - upper boundary ! 2. arg - lower boundary ! 3. arg - pointer to source string ! 4. arg - pointer to destination string ! 5. arg - size of destination string ! Output: AX - pointer to destination string ! Registers changed: AX, BX, CX, DX ! strsub: push bp mov bp,sp push si push di mov di,[bp + 10] ! get pointer to destination string mov cx,[bp + 12] ! get destination size mov si,[bp + 8] ! get pointer to source string mov bx,[bp + 6] ! get lower boundary mov dx,[bp + 4] ! get upper boundary add bx,si ! compute lower boundary pointer add dx,si ! compute upper boundary pointer jcxz strs9 cld strs1: lodsb or al,al ! end of source reached jz strs9 cmp si,bx jbe strs1 ! loop until lower boundary reached stosb cmp si,dx ja strs9 ! terminate if upper boundary reached loop strs1 strs9: mov byte ptr [di],#0 ! terminate destination mov ax,[bp + 10] ! get pointer to destination string pop di pop si pop bp ret ! !************************************************************************** ! ! Crypt a string (not implemented yet) ! Input: 1. arg - pointer to source string ! 2. arg - pointer to destination string ! 3. arg - size of destination string ! Output: AX - pointer to destination string ! Registers changed: AX, BX ! crypt: push bp mov bp,sp mov bx,[bp + 6] mov byte ptr [bx],#0 mov ax,bx pop bp ret ! !************************************************************************** ! ! Load a new operating system ! Input: 1. arg - pointer to filename string ! 2. arg - first word of gateway address in network order ! 3. arg - second word of gateway address in network order ! 4. arg - first word of server address in network order ! 5. arg - second word of server address in network order ! Output: Routine does not return ! Registers changed: all ! load: push bp mov bp,sp push es mov ax,cs mov es,ax seg es mov al,[userom] ! if we dont have a rom, just return or al,al jz load5 ! Check for device name in file name cld mov di,#devstr ! get pointer to /dev/ string mov si,[bp + 4] ! get pointer to filename mov cx,#5 rep ! check if the file name starts cmpsb ! with /dev/ pop es jne load7 call decodestr ! decode device string jc load7 call lddisk ! load from disk, returns if error load7: seg cs les di,[bootp] mov ax,es or ax,di jnz load1 ! if we have no BOOTP record, just load5: mov bx,#1 ! return to the bootrom immediately jmp near imgret ! Save the new values into the BOOTP record and then call the bootrom load1: cld mov si,[bp + 4] ! get pointer to filename mov cx,#BOOTP_MAX_FNAME - 1 ! get maximum filename size push di add di,#BOOTP_FNAME ! determine dest addr in BOOTP record load2: lodsb or al,al jz load3 ! copy until end of string stosb loop load2 load3: jcxz load4 xor al,al rep ! fill remainder with zeros stosb load4: pop di mov ax,[bp + 10] ! get server address, first word mov bx,[bp + 12] ! second word mov cx,ax or cx,bx ! check if we have an address at all jz load6 seg es mov BOOTP_SERVER + 0[di],ax seg es ! move into BOOTP record mov BOOTP_SERVER + 2[di],bx load6: mov ax,[bp + 6] or ax,[bp + 8] ! check if we have a gateway address jz load5 mov ax,#BOOTP_RFC_ROUTE call deltag ! delete gateway address tag jc load5 seg cs mov ax,word ptr [bootp + 0] add ax,#BOOTP_MAX_SIZE - 8 ! we have to have enough space to cmp di,ax ! add gateway tag jae load5 cld mov al,#BOOTP_RFC_ROUTE stosb ! add new tag mov al,#4 stosb ! add new size mov ax,[bp + 6] ! load gateway address, first word stosw mov ax,[bp + 8] ! second word stosw mov al,#BOOTP_RFC_END stosb jmp load5 ! load new operating system ! String in the file name indicating a load from a local disk devstr: .ascii "/dev/" ! !************************************************************************** ! ! Load a disk boot sector into memory ! Input: AL - disk drive ID ! AH - partition number ! Output: returns if not successful, otherwise doesnt return ! Registers changed: AX, BX, CX, DX, SI if routine returns ! lddisk: push es push ds mov bx,ds add bx,#$1000 ! set temporary buffer at end of mov es,bx ! MGL program data/stack segment mov cx,cs mov ds,cx ! set DS correctly mov [partnum],ah ! save partition number for later mov cl,ah mov dl,al ! the drive number goes into DL xor ah,ah int $13 ! reset disk system ! First load the master boot record. xor bx,bx or cl,cl ! if we have to load the master jz lddsk4 ! boot block we dont need to use mov bx,#PART_BUF_OFS ! the partition table buffer lddsk4: mov cx,#$0001 ! load first sector on track 0 xor dh,dh ! head 0 call reads1 ! load the sector jc lddsk9 ! Next load the first sector of the partition. If we are to load from a ! partition in an extended partition, we have to read the extended partition ! table first. mov bl,[partnum] or bl,bl ! check if thats all jz lddsk3 cmp bl,#5 ! check if we have to load from an jb lddsk6 ! extended partition mov bx,#PART_OFS ! search for an extended partition mov cx,#ENTRY_NUM lddsk1: seg es cmp byte ptr PART_TYPE_OFS[bx],#EXT_PART_TYPE je lddsk2 add bx,#ENTRY_LEN ! continue with next entry loop lddsk1 jmp lddsk9 lddsk2: mov si,bx mov bx,#PART_BUF_OFS call readsect ! read extended partition table jc lddsk9 mov bl,[partnum] ! compute partition number in extended sub bl,#4 ! table cmp bl,#5 jae lddsk9 ! Now really load the first sector of the boot partition lddsk6: dec bl ! partition numbers start with 1 xor bh,bh mov cl,#4 shl bx,cl ! compute offset into partition table lea si,PART_OFS[bx] xor bx,bx call readsect ! read first sector of boot partition jnc lddsk3 lddsk9: pop ds ! return in case of error pop es ret ! After the boot sector has been read, we can disable all bootrom services, ! restore the interrupt vectors etc., then copy the boot sector to where it ! belongs, and finally call it. lddsk3: xor ah,ah ! reset disk system int $13 push es push dx ! save disk ID for later push si ! save offset to partition table entry call intrestore ! restore old interrupts call termrom ! terminate bootrom services pop bx pop dx pop ds cli xor ax,ax mov ss,ax mov sp,#BOOT_BLOCK - 2 ! set stack to beginning of boot block sti cld xor si,si mov es,si ! copy boot sector and partition table mov di,#BOOT_BLOCK ! to their final destination mov cx,#SECTSIZE rep movsw xor ax,ax mov ds,ax ! restore pointer to partition entry lea si,BOOT_BLOCK[bx] ! in DS:SI jmpi BOOT_BLOCK,0 ! call the boot sector ! !************************************************************************** ! ! Read first sector of partition. reads1 is an additional subroutine entry ! point! ! Input: ES:SI - pointer to partition table entry ! ES:BX - pointer to sector buffer ! DL - disk drive ID ! Output: Carry flag set if error ! Registers changed: AX, CX, DH ! readsect: seg es mov al,PART_TYPE_OFS[si] or al,al ! check if partition table entry jz reads8 ! is valid seg es mov cx,PART_SECT_OFS[si] ! get sector, track and head seg es ! number from partition table mov dh,PART_HEAD_OFS[si] reads1: mov ax,#$0201 ! actually read the sector int $13 jc reads8 seg es cmp word ptr BOOT_SIG_OFS[bx],#BOOT_SIG je reads9 reads8: stc reads9: ret ! !************************************************************************** ! ! Decode device string ! Input: SI - pointer to string ! Output: AL - disk drive ID ! AH - partition number ! Carry flag set if error ! Registers changed: AX, SI ! decodestr: ! First decode the first two letters. The string has to start either ! with 'fd' or 'hd'. We dont any other devices yet. mov ah,[si + 2] mov al,#HD_BASE cmp word ptr [si],#$6468 ! check for hard disk 'hd' je decod1 mov al,#FD_BASE cmp word ptr [si],#$6466 ! check for floppy disk 'fd' jne decod8 ! OK, we have to look for a floppy device. After the initial 'fd' a number ! has to follow. The maximum is 4 disk drives, so check that its in the ! range 0 to 3. sub ah,#$30 ! check for correct number jb decod8 cmp ah,#3 ja decod8 add al,ah ! compute final device number xor ah,ah ! partition number is always zero add si,#3 ! let BX point after last character jmp decod7 ! Now decode the harddisk description. The initial 'hd' has to be followed ! by a letter from 'a' to 'd' indicating the drive number, followed by a ! number indicating the partition number. If the number is missing, 0 is ! assumed, which means the whole disk, e.g. the master boot block. decod1: sub ah,#$61 ! check for correct letter jb decod8 cmp ah,#3 ja decod8 add al,ah ! compute final device number add si,#3 ! let BX point after last character mov ah,[si] or ah,ah ! end of string means "zero" jz decod7 sub ah,#$30 ! check for correct partition jb decod8 cmp ah,#8 ! maximum number of 8 partitions ja decod8 inc si decod7: cmp byte ptr [si],#0 ! string has to be followed by a null je decod9 ! return without error decod8: stc ! return with error decod9: ret ! !************************************************************************** ! ! Get a tag string from a BOOTP record ! Input: 1. arg - tag number ! 2. arg - pointer to destination string ! 3. arg - size of destination string ! Output: AX - pointer to destination string ! Registers changed: AX, BX, CX ! getbootp: push bp mov bp,sp push si push di mov di,[bp + 6] ! get pointer to destination mov cx,[bp + 8] ! get size of destination jcxz getbp9 ! nothing to be done if dest size zero mov ax,[bp + 4] ! get BOOTP tag number push es mov si,di call fndtag ! find BOOTP tag xchg si,di mov ax,es ! AX:SI - pointer to BOOTP tag pop es ! DS&ES:DI - pointer to destination jc getbp9 ! couldnt find the tag cld push ds mov ds,ax ! DS:SI - pointer to BOOTP tag mov cx,[bp + 8] ! get size of destination lodsb ! load tag size xor ah,ah cmp ax,cx ! select the smaller of the two sizes jae getbp1 mov cx,ax getbp1: rep movsb ! copy the tag string into destination pop ds getbp9: mov byte ptr [di],#0 ! terminate destination mov ax,[bp + 6] ! get pointer to destination string pop di pop si pop bp ret ! !************************************************************************** ! ! Put a string into a BOOTP tag ! Input: 1. arg - tag number ! 2. arg - pointer to source string ! Output: none ! Registers changed: AX, BX, CX, DX ! putbootp: push bp mov bp,sp push si push di push es mov ax,[bp + 4] ! get tag number call deltag ! delete tag jc putbp9 mov ax,[bp + 4] ! get new tag number mov si,[bp + 6] ! get source string seg cs mov dx,word ptr [bootp + 0] ! determine maximum end address add dx,#BOOTP_MAX_SIZE - 2 mov bx,di add bx,#256 ! should not be farther away than 256 cmp bx,dx ! bytes, since the tag size has just jae putbp1 ! a size of one byte mov dx,bx putbp1: sub bx,#256 - 3 ! check that we have at least enough cmp bx,dx ! space for tag number, size and end tag jae putbp9 cld stosb ! store new tag number mov bx,di ! save address of size byte for later inc di putbp6: cmp di,dx ! check if end of buffer reached jae putbp7 lodsb ! get next source byte or al,al jz putbp7 ! end of string reached? stosb ! store into new bootp tag jmp putbp6 putbp7: mov ax,di sub ax,bx ! compute size of new tag dec ax seg es mov byte ptr [bx],al ! save tag size mov al,#BOOTP_RFC_END stosb ! store end tag putbp9: pop es pop si pop di pop bp ret ! !************************************************************************** ! ! Find a BOOTP tag ! Input: AX - tag number ! Output: ES:DI - pointer to first byte after tag number ! Carry flag set if not found ! Registers changed: AX, BX, CX, DI, ES ! fndtag: or ah,ah jnz fndtg8 ! check that tag is correct seg cs les di,[bootp] mov cx,es ! check that we have a BOOTP record or cx,di ! at all jz fndtg8 mov ah,al add di,#BOOTP_VEND + BOOTP_MAGIC_LEN seg cs mov cx,[btplen] sub cx,#BOOTP_VEND + BOOTP_MAGIC_LEN jc fndtg8 fndtg1: seg es mov al,[di] ! get next tag inc di cmp al,ah ! found it, this also clears carry je fndtg9 dec cx jz fndtg8 ! check if end reached cmp al,#BOOTP_RFC_NOP ! handle NOP tag je fndtg1 cmp al,#BOOTP_RFC_END ! handle END tag je fndtg8 seg es ! handle all other tags mov bl,[di] xor bh,bh inc di add di,bx ! jump to next tag dec cx sub cx,bx ja fndtg1 ! read next tag fndtg8: stc fndtg9: ret ! !************************************************************************** ! ! Delete a BOOTP tag ! Input: AX - tag number ! Output: ES:DI - pointer to end tag in BOOTP record ! Carry flag set if error ! Registers changed: AX, BX, CX, DX, SI, DI, ES ! deltag: seg cs les di,[bootp] mov bx,es or bx,di ! check that we have a BOOTP record jz deltg8 ! at all seg cs mov dx,[btplen] or dx,dx ! compute end of BOOTP record jz deltg8 add dx,di or ah,ah ! check that its correct jnz deltg8 cmp al,#BOOTP_RFC_NOP je deltg8 cmp al,#BOOTP_RFC_END ! cannot delete special tags je deltg8 ! Find BOOTP tag and delete it if it exists, by copying the remainder ! of the record into the tag to be deleted. call fndtag ! find BOOTP tag jc deltg3 ! not found push ds mov ax,es mov ds,ax mov si,di dec di cld lodsb ! find the end of the current tag xor ah,ah add si,ax deltg1: cmp si,dx ! check if end of BOOTP record reached jae deltg2 lodsb ! get next tag cmp al,#BOOTP_RFC_END je deltg2 ! handle END tag stosb ! store into old tag cmp al,#BOOTP_RFC_NOP je deltg1 ! handle NOP tag lodsb stosb ! copy size byte mov cl,al xor ch,ch rep movsb ! copy tag jmp deltg1 deltg2: seg es mov byte ptr [di],#BOOTP_RFC_END pop ds jmp deltg7 ! return without error ! We dont have the required tag, so we have to at least find the end tag. deltg3: mov al,#BOOTP_RFC_END call fndtag ! find end tag jnc deltg4 seg cs les di,[bootp] ! end tag not found??? seg cs add di,[btplen] ! add BOOTP length inc di deltg4: dec di ! decrement back to end tag deltg7: clc jmp deltg9 deltg8: stc deltg9: ret ! !************************************************************************** ! ! ANSI control sequences supported by the runtime module: ! ! Display attributes ! ! Code Effect ! [0m normal text ! [1m high-intensity on ! [21m high-intensity off ! [5m blinking on ! [25m blinking off ! [7m reverse video on ! [27m reverse video off ! [3xm set foreground color: ! [4xm set background color. x can be: ! ! 0 - black 4 - blue ! 1 - red 5 - magenta ! 2 - green 6 - cyan ! 3 - yellow 7 - white ! ! ! Cursor control ! ! Code Effect ! [r;cH move cursor to row r col c (r and c are both numbers) ! [rA move cursor up r rows ! [rB move cursor down r rows ! [cC move cursor right c columns ! [cD move cursor left c columns ! [?7l turn off line wrap ! [?$7 turn on line wrap ! [J clear screen and home cursor ! [s save the cursor position ! [u return cursor to saved position ! !************************************************************************** ! ! ANSI control sequence command table ! macro tab .byte '?1 ! put letter into table' .word cmd_?2?1 ! put offset to cmd routine mend cmdtab: ! Define commands for uppercase letters tab (A,u) tab (B,u) tab (C,u) tab (D,u) tab (H,u) tab (J,u) ! Define commands for lowercase letters tab (h,l) tab (l,l) tab (m,l) tab (s,l) tab (u,l) ! Mark end of table .byte 0 TABSIZE equ 3 ! size of each table entry ! !************************************************************************** ! ! Display a character and interpret any ANSI escape sequence. ! Input: AL - character to display ! Output: none ! Registers changed: AX ! outchar: push bx push cx push dx push ds mov bx,cs ! set data segment correctly mov ds,bx ! Determine the current cursor position. push ax mov ah,#$03 mov bh,[scnpage] int $10 mov [curpos],dx pop ax ! State switcher. Depending on the character received with this interrupt ! routine the state will be switched and the corresponding action executed. mov ah,[dispstate] cmp ah,#NORMAL_STATE ! first handle normal state jne outch2 cmp al,#CHR_ESC ! is it an escape character? jne outch1 mov byte ptr [dispstate],#ESC_STATE ! yes, just switch the state and jmp outchF ! terminate outch1: call putchar ! no, we have to print the jmp outch3 ! character outch2: cmp ah,#ESC_STATE jne outch4 ! are we in escape state? cmp al,#CHR_BRACKET jne outch3 ! a bracket has to follow mov byte ptr [dispstate],#BRACKET_STATE ! simply switch the state jmp outchF ! and terminate outch3: mov byte ptr [dispstate],#NORMAL_STATE ! if no bracket, this is an outchF: jmp outch9 ! error, so switch back to ! normal mode outch4: cmp ah,#BRACKET_STATE ! check for bracket state jne outch5 cmp al,#CHR_QUEST ! skip any question marks je outchF cmp al,#CHR_EQUAL ! skip any equal signs je outchF mov byte ptr [dispstate],#NUMBER_STATE ! continue with number state mov byte ptr [argnum],#0 if ARGMAX = 2 mov word ptr [argbuf],#0 ! zero out argument buffer else mov bx,#ARGMAX outchD: mov byte ptr argbuf[bx],#0 ! zero out argument buffer dec bx jnz outchD endif jmp outch6 outch5: cmp ah,#NUMBER_STATE ! if we are not in number state its an jne outch3 ! error, as we dont have anymore states outch6: cmp al,#CHR_COLON jne outch7 ! a semicolon sign advances the argument mov al,[argnum] ! counter inc al cmp al,#ARGMAX ! no more than ARGMAX arguments allowed, jae outch9 ! simply skip the rest mov [argnum],al jmp outch9 outch7: cmp al,#CHR_ZERO ! check if we got a number here jb outch8 cmp al,#CHR_NINE ja outch8 sub al,#CHR_ZERO ! convert character into number mov cl,al mov bl,[argnum] xor bh,bh mov al,argbuf[bx] ! multiply the number in the buffer mov ah,#10 ! by 10 mul ah add al,cl ! and add the new value mov argbuf[bx],al ! set new value in argument buffer jmp outch9 ! thats it ! Now execute the command associated with the last character of the escape ! sequence. outch8: xor bx,bx outchA: mov ah,cmdtab[bx] or ah,ah ! end of table reached je outchC cmp al,ah ! found character je outchB add bx,#TABSIZE ! continue with next table entry jmp outchA outchB: mov ax,word ptr cmdtab + 1[bx] mov dx,[curpos] call ax ! call cmd handling routine outchC: mov byte ptr [dispstate],#NORMAL_STATE ! restore state counter ! Terminate the interrupt routine by restoring all registers outch9: pop ds pop dx pop cx pop bx ret ! !************************************************************************** ! ! Actually display a character onto the console ! Input: AL - character to display ! DX - current cursor position ! Output: none ! Registers used: AX, BX, CX, DX ! putchar: mov bh,[scnpage] ! used throughout this routine ! The bell character is best handled by the BIOS cmp al,#CHR_BELL jne putch2 putch1: mov ah,#$0E ! print special character using the int $10 ! BIOS jmp putch9 ! TABs are not handled by the BIOS teletype driver, so we have to do it ! ourselves. putch2: cmp al,#CHR_TAB ! check for tab character jne putch4 mov cl,dl ! compute number of spaces to print or cl,#$F8 neg cl xor ch,ch putch3: push cx mov al,#CHR_SPACE ! print spaces call putchar ! recursive call pop cx loop putch3 jmp putch9 ! Carriage return simply involves setting the cursor position to zero putch4: cmp al,#CHR_CR ! check for carriage return jne putch5 xor dl,dl ! move cursor into column zero jmp putcursor ! Linefeed involves incrementing the line number and scrolling the screen if ! necessary putch5: cmp al,#CHR_LF ! check for linefeed je putch8 ! the linefeed code is below ! Backspace simply involves decrementing the current cursor position cmp al,#CHR_BS ! check for backspace jne putch6 or dl,dl ! dont decrement below zero jz putcursor dec dl ! decrement column number jmp putcursor ! All other characters are printed at the cursor position putch6: mov ah,#$09 mov bl,[scnattr] ! print the character using BIOS mov cx,#1 int $10 inc dl ! increment cursor position cmp dl,[scncolumns] ! check if at right end of line jb putcursor dec dl ! restore cursor to rightmost test byte ptr [flags],#FLAG_NOWRAP ! column and check if we should jnz putcursor ! wrap at the end of the line xor dl,dl ! put cursor into column 0 putch8: inc dh cmp dh,#MAXLINES ! check if at bottom of screen jb putcursor dec dh ! restore cursor to lowest screen line push dx mov ax,#$0601 xor cx,cx ! define upper left corner mov dl,[scncolumns] ! define lower right corner dec dl mov bh,[scnattr] int $10 ! use the BIOS to scroll the screen pop dx ! Put cursor to position inidicated by the curpos variable putcursor: mov [curpos],dx ! save new cursor position mov ah,#$02 mov bh,[scnpage] ! set cursor using BIOS int $10 putch9: ret ! !************************************************************************** ! ! Handle escape sequence 'A': move cursor up ! Input: DX - current cursor position ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_uA: sub dh,[argbuf + 0] ! decrement line number jae putcursor ! set new cursor position ret ! !************************************************************************** ! ! Handle escape sequence 'B': move cursor down ! Input: DX - current cursor position ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_uB: add dh,[argbuf + 0] ! increment line number cmp dh,#MAXLINES jb putcursor ! set new cursor position ret ! !************************************************************************** ! ! Handle escape sequence 'C': move cursor right ! Input: DX - current cursor position ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_uC: add dl,[argbuf + 0] ! increment column number cmp dl,[scncolumns] jb putcursor ! set new cursor position ret ! !************************************************************************** ! ! Handle escape sequence 'D': move cursor left ! Input: DX - current cursor position ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_uD: sub dh,[argbuf + 0] ! decrement column number jae putcursor ! set new cursor position ret ! !************************************************************************** ! ! Handle escape sequence 'H': move cursor to specified position ! Input: none ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_uH: mov dx,[argbuf + 0] xchg dh,dl ! get new cursor position jmp putcursor ! !************************************************************************** ! ! Handle escape sequence 'J': clear screen and move cursor into upper ! left corner of the screen ! Input: none ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_uJ: mov ax,#$0600 mov bh,[scnattr] xor cx,cx mov dh,#MAXLINES - 1 ! use the BIOS scrolling function mov dl,[scncolumns] ! to clear the screen dec dl int $10 xor dx,dx ! put cursor into upper left corner jmp putcursor ! !************************************************************************** ! ! Handle escape sequence 'h': set display option, currently only option 7 ! is supported, which turns line wrapping on. ! Input: none ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_lh: mov al,[argbuf + 0] cmp al,#7 ! check for option 7 jne cmdlh9 and byte ptr [flags],#~FLAG_NOWRAP ! turn wrapping off cmdlh9: ret ! !************************************************************************** ! ! Handle escape sequence 'l': reset display option, currently only option 7 ! is supported, which turns line wrapping off. ! Input: none ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_ll: mov al,[argbuf + 0] cmp al,#7 ! check for option 7 jne cmdll9 or byte ptr [flags],#FLAG_NOWRAP ! turn wrapping off cmdll9: ret ! !************************************************************************** ! ! Handle escape sequence 'm': set screen attributes according to the ! following table: ! ! [0m normal text ! [1m high-intensity on ! [21m high-intensity off ! [5m blinking on ! [25m blinking off ! [7m reverse video on ! [27m reverse video off ! [3xm set foreground color ! [4xm set background color ! ! Input: none ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_lm: ! First set the global variables according to the first parameter of the ! escape sequence mov ah,[flags] mov al,[argbuf + 0] or al,al jnz cmdlm1 and ah,#FLAG_NORMAL ! set normal mode jmp cmdlm9 cmdlm1: mov cl,al cmp cl,#20 ! check for reset command jb cmdlm2 sub cl,#20 cmdlm2: cmp cl,#1 jne cmdlm3 mov ch,#FLAG_INTENSE ! set high-intensity mode jmp cmdlm5 cmdlm3: cmp cl,#5 jne cmdlm4 mov ch,#FLAG_BLINK ! set blinking mode jmp cmdlm5 cmdlm4: cmp cl,#7 jne cmdlm7 mov ch,#FLAG_INVERS ! set inverse mode cmdlm5: cmp al,#20 jae cmdlm6 or ah,ch ! set the flag jmp cmdlm9 cmdlm6: not ch and ah,ch ! reset the flag jmp cmdlm9 cmdlm7: sub al,#30 jb cmdlmF ! check for foreground color cmp al,#7 ja cmdlm8 mov [fg_color],al jmp cmdlm9 cmdlm8: sub al,#10 jb cmdlmF ! check for background color cmp al,#7 ja cmdlmF mov [bg_color],al ! Now set the actual attribute value according to the flags set previously cmdlm9: mov [flags],ah xor ch,ch test ah,#FLAG_BLINK jz cmdlmA or ch,#%10000000 ! set blink attribute cmdlmA: test ah,#FLAG_INTENSE jz cmdlmB or ch,#%00001000 ! set high-intensity attribute cmdlmB: mov cl,#4 mov bl,[fg_color] mov bh,[bg_color] ! set colors test ah,#FLAG_INVERS jz cmdlmC shl bl,cl ! shift foreground color into background jmp cmdlmD ! for inverse display cmdlmC: shl bh,cl ! shift background color cmdlmD: or bl,bh and bl,#%01110111 ! compute color attribute value or ch,bl ! and merge it into the final value mov [scnattr],ch ! save final screen attributes cmdlmF: ret ! !************************************************************************** ! ! Handle escape sequence 's': save current cursor position ! Input: none ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_ls: mov ax,[curpos] mov [savedpos],ax ! save the current cursor position ret ! !************************************************************************** ! ! Handle escape sequence 'u': restore cursor position ! Input: none ! Output: none ! Registers used: AX, BX, CX, DX ! cmd_lu: mov dx,[savedpos] jmp near putcursor ! place cursor to new position ! !************************************************************************** ! ! PXE support routines ! !************************************************************************** ! ! Call PXE API entry point ! Input: AX - PXE function number ! SS:BP - pointer to PXE parameter structure ! Output: AX - error code ! Registers changed: AX ! callpxe: push ss push bp push ax seg cs call far ptr [pxentry] mov ax,word ptr [bp] add sp,#6 ret ! !************************************************************************** ! ! Call GEN_UNLOAD_STACK to remove the kernel from memory ! Input: none ! Output: AX - status ! Registers changed: AX ! do_unload_base: mov ax,#GEN_UNLOAD_STACK jmp ustrt1 ! !************************************************************************** ! ! Call GEN_STOP_BASE to reset terminate bootrom services. ! Input: none ! Output: AX - status ! Registers changed: AX ! do_stop_base: mov ax,#GEN_STOP_BASE jmp ustrt1 ! !************************************************************************** ! ! Call UNDI_STOP_UNDI to reset all interrupt vectors. ! Input: none ! Output: AX - status ! Registers changed: AX ! do_stop_undi: mov ax,#UNDI_STOP_UNDI jmp ustrt1 ! !************************************************************************** ! ! Call UNDI_CLEANUP to remove the network driver from memory. ! Input: none ! Output: AX - status ! Registers changed: AX ! do_undi_cleanup: mov ax,#UNDI_CLEANUP ustrt1: push bp sub sp,#2 ! all 'stop' routines only return mov bp,sp ! a status word, so we only need call callpxe ! to allocate 2 bytes on the stack add sp,#2 ! for all routines pop bp ret ! !************************************************************************** ! ! Terminate all bootrom services including the kernel and UNDI, and then ! remove everything from memory if possible. ! Input: none ! Output: none ! Registers changed: AX, BX, CX, DX, SI, DI, DS, ES ! termrom: mov ax,cs mov ds,ax ! If we have an old bootrom with a services interrupt we can simply ! call it and it will do everything. mov ax,word ptr [pxentry + 0] or ax,word ptr [pxentry + 2] jnz termr1 mov ah,#1 int SERVICE_INT ret ! Unload both, the kernel and the UNDI driver termr1: call do_stop_base ! stop the base code or ax,ax jnz termr2 call do_unload_base ! unload base code termr2: push ax ! save return status call do_stop_undi ! stop UNDI services or ax,ax jnz termr3 call do_undi_cleanup ! terminate network driver termr3: pop bx ! restore STOP_BASE return status or bx,bx ! check if we can remove the kernel jnz termr9 ! from memory mov dx,[endseg] or ax,ax ! if an error occurred during STOP_UNDI jz termr4 ! we cant reclaim the UNDI memory, as mov dx,[drvseg] ! there might be some interrupt not termr4: mov cl,#6 ! restored shr dx,cl mov bx,#BIOS_SEG mov es,bx seg es mov word ptr [BIOS_FREEMEM],dx termr9: ret ! !************************************************************************** ! ! Variable definitions for runtime module ! scnmode: .byte 0 ! current screen mode scnpage: .byte 0 ! current screen page scnattr: .byte $07 ! attributes for characters to print scncolumns: .byte 0 ! number of screen columns curpos: .word 0 ! current cursor position savedpos: .word 0 ! saved cursor position flags: .byte 0 ! display flags fg_color: .byte $07 ! foreground color bg_color: .byte 0 ! background color dispstate: .byte NORMAL_STATE ! current display state argbuf: .space ARGMAX ! argument number buffer argnum: .byte 0 ! current argument number userom: .byte 0 ! flag if we were started by a bootrom partnum: .byte 0 ! partition number to load intbuf: .space INTBUFLEN ! buffer for integer conversions cntend: .long 0 ! timeout counter end value pxestruct: .long 0 ! far pointer to !PXE structure pxentry: .long 0 ! far pointer to PXE entry point drvseg: .word 0 ! segment of UNDI driver endseg: .word 0 ! end segment of bootrom memory ! !************************************************************************** ! end