From: joepower@ix.netcom.com (Joseph Power)
Newsgroups: alt.lang.asm
Subject: Creating a BEE
Date: 27 Sep 1995 15:02:49 GMT
I was asked to elaborate on how to use a .COM file as a BIOS extension (Which I
refer to as a BEE.) Rather than respond in email, I thought I'd post an example
for all to see.
First we need an assembly program that will be assembled in TINY model (Note the
non-standard ORG statement):
TITLE Demonstration BIOS Extension EPROM (BEE)
.LIST
.CREF
.MODEL tiny
;-----------------------------------------------------------------------------
; file: BEE.ASM
; purpose: allows user to run our code at boot-up
; authors: Joseph R Power
; history: 18-December-94 JRP created
; 27-September-95 modified to be released on alt.lang.asm
;
; notes: This code is meant to be burned into an EPROM
; DS and ES are preserved so BIOS won't crash.
;
; Copyright (C) 1994 Landmark Research International Corporation
;-----------------------------------------------------------------------------
;-----------------------------------------------------------------------------
; Predefined symbols
;-----------------------------------------------------------------------------
; -- flags (comment out to deactivate) --
; DEBUG = "T" ; activate debug printing
; -- possible BEE sizes (uncomment only one) --
BEE_SIZE = 10h ; number of .5K pages in 8K
; BEE_SIZE = 20h ; number of .5K pages in 16K
; BEE_SIZE = 40h ; number of .5K pages in 32K
; BEE_SIZE = 80h ; number of .5K pages in 64K
; -- ASCII control characters --
aNUL = 00h ; Null
aSOH = 01h ; Start Of Header
aSTX = 02h ; Start Of Text
aETX = 03h ; End Of Text
aEOT = 04h ; End Of Transmission
aENQ = 05h ; Inquire
aACK = 06h ; Acknowledge
aBELL = 07h ; Bell
aBS = 08h ; Back Space
aHT = 09h ; Horizontal Tab
aLF = 0Ah ; Line Feed
aVT = 0Bh ; Vertical Tab
aFF = 0Ch ; Form Feed
aCR = 0Dh ; Carriage Return
aSO = 0Eh ; Shift Out
aSI = 0Fh ; Shift In
aDLE = 10h ; Data Link Escape
aDC1 = 11h ; Device Control 1
aDC2 = 12h ; Device Control 2
aDC3 = 13h ; Device Control 3
aDC4 = 14h ; Device Control 4
aNAK = 15h ; Negative Acknowledge
aSYN = 16h ; Syncronous
aETB = 17h ; End of Transmission Block
aCAN = 18h ; Cancel
aEM = 19h ; End Of Medium
aSUB = 1Ah ; Substitute
aESC = 1Bh ; Escape
aFS = 1Ch ; Field Seperator
aGS = 1Dh ; Group Seperator
aRS = 1Eh ; Record Seperator
aUS = 1Fh ; Unit Seperator
ENTERSCAN = 1Ch ;
BACKSCAN = 0Eh ; BACK SPACE SCAN CODE
POUNDSIGN = 35 ; Ascii character "#"
; -- timer constants --
SPEAKER = 61h ; port for turning off the speaker
TIMER0 = 40h ; timer base port
TIMER2 = 42h ; timer 2 (speaker timer, channel 2)
TIMERSETUP = 43h ; port address for setting timer modes
DELAYTIME = 20 * 1154 ; delay time (in milliseconds)
TIMETOWAIT = 600 ; number of loops for 10 seconds
TIMELOOPS = TIMETOWAIT * (1000 / DELAYTIME) ; # of loops to wait
total_time = 0h ; offset for total clock ticks counter
; -- segment addresses --
TIMERSEG = 0F00H ; addr where our time counter will go
BUFSEG = 2000H ; addr of input buffer if BEE in ROM
; -- input buffer --
BUFLEN = 80
INBUF = 0
; -- interrupt vector numbers --
VIDINT = 10h ; BIOS video interrupt
KBDINT = 16h ; Keyboard interrupt
CLKINT = 08h ; Clock Interrupt
dosintoff = 84h ; where DOS int offset located
dosintseg = 86h ; where DOS int segment located
.CODE
;-----------------------------------------------------------------------------
; name: init
; purpose: identifies code as BIOS extension & holds all messages and tables
; entry: none
; exit: none
; history: 18-December-93 JRP created
; notes: do NOT use .STARTUP directive
;-----------------------------------------------------------------------------
init proc near
org 0 ; makes code ROMable
db 055h ; indicates BIOS extension
db 0AAh ;
db BEE_SIZE ; indicate size
jmp bee ; skip over messages
;-----------------------------------------------------------------------------
; area reserved for checksum
;-----------------------------------------------------------------------------
dw 0 ; use a word so chksum can go at offset 6
; whether the jmp is 2 or 3 bytes long
;-----------------------------------------------------------------------------
; Messages
;-----------------------------------------------------------------------------
msgA BYTE aCR,aLF,"Demonstration BEE",
aCR,aLF,"Copyright (C) 1994 by Landmark Research Int'l.
Corp.",
aCR,aLF,
aCR,aLF,"Press D to run Diagnostics, C to continue.",
aCR,aLF
msgAlen = LENGTHOF msgA
DIAG_YES = 'D'
DIAG_NO = 'C'
MSGC BYTE ACR,ALF,"Booting",ACR,ALF
msgClen = LENGTHOF msgC
msgD BYTE aCR,aLF,"Running Diagnostics",aCR,aLF
msgDlen = LENGTHOF msgD
DOSMSG BYTE aCR,aLF,"DOS Int access attempted. Press a key to
continue.",aCR,aLF
DOSMSGLEN = LENGTHOF DOSMSG
init endp
;-----------------------------------------------------------------------------
; name: bee
; purpose: possibly revector interrupts, put up message & wait for key
; entry: nothing
; exit: nothing
; history: 18-December-93 JRP created
; notes:
;-----------------------------------------------------------------------------
bee proc near
cli ; preserve current ss, sp in cx, bx
mov bx,sp ; in case we return to BIOS
mov ax,ss
mov cx,ax
mov ax,BUFSEG ; now set up our own stack to insure
mov dx,0fff0h ; we have plenty of stack space
mov ss,ax
mov sp,dx
push cx ; put former ss, sp on new stack
push bx
push es ; save required registers
push ds
mov ax,cs ; initialize ds
mov ds,ax ; our environment is now set
beeA:
; clear the screen and home the cursor
mov ah,0Fh ; get current mode
int VIDINT
mov ah,0 ; set current mode (clears screen)
int VIDINT
mov ah,2 ; home the cursor
xor bh,bh ; assume page 0
xor dx,dx ; (0,0)
int VIDINT
mov bp,offset msgA ; let world know we're alive
mov cx,msgAlen ; # of chars to write
call print
beeB:
call flush ; get rid of any pending chars
call getchtimed ; wait for a keystroke
and al,0DFh ; make sure it is upper case
cmp al,DIAG_YES ; see if 'D'
je beeD
cmp al,DIAG_NO ; only accept D or C
jne beeB
beeC:
mov bp,offset msgC ; show that we didn't choose D
mov cx,msgClen ; cx is # of chars to write
call print ; print msg to console
jmp bee_exit ; and return to BIOS
beeD:
mov bx,offset doshandler ; alert user to dos access attempts
mov ax,cs
cli
mov si,dosintoff
mov ds:[si],bx
mov si,dosintseg
mov ds:[si],ax
mov bp,offset msgD ; show that we didn't choose D
mov cx,msgDlen ; cx is # of chars to write
call print ; print msg to console
jmp bee_exit ; and return to BIOS
; NOTES - This is where I would normally set the system up to run my BIOS
; extension code. After that I would jump to it and begin execution.
; Because our code contains diagnostics which affect many parts of the
; system, I normally have it simply reboot the machine when it is
; finished (to protect the user).
; Please note that this code has taken over the DOS interrupt vector
; without saving the previous contents. This is not a problem because
; DOS will take the vector back when it loads. This is just a safety
; precaution to prevent mixed language BEEs (I use assembler and C)
; from accidently going crazy. That's my job.
bee_exit:
pop ds ; restore required registers
pop es
pop bx ; restore former ss, sp so we can
pop ax ; return to BIOS
cli ; (interrupts must be off during this)
mov sp,bx
mov ss,ax
sti ; interrupts now ok
retf ; return to BIOS
bee endp
;-----------------------------------------------------------------------------
; support routines
;-----------------------------------------------------------------------------
;-----------------------------------------------------------------------------
; name: getchtimed
; purpose: wait some fixed time until key has been pressed and return it
; entry: nothing
; exit: ah = scan code al = ASCII character
; history: 27-February-93 CAH created
; notes: won't return until a key has been pressed or time expired
;-----------------------------------------------------------------------------
getchtimed proc near
sti
push cx
mov cx, TIMETOWAIT
getloop:
mov ah,01 ; read kbd status function
int KBDINT ; keyboard handler
jnz gotone ; wait until keystroke available or time out
call std_delay ; waste some time
loop getloop ; time's not up, try again
mov al,'C' ; return C to continue Boot
jmp endchar
gotone:
mov ah, 0 ; read kbd char function
int KBDINT ; keyboard handler
endchar:
ifdef DEBUG ; DEBUG
PUSH DX ; debug print: COM port number
push ax
call printch
CALL STD_DELAY
pop ax
pop dx
endif ; DEBUG
pop cx
ret ; return keystroke
getchtimed endp
;-----------------------------------------------------------------------------
; name: print
; purpose: sends string at BP, length CX to console
; entry: BP = string address offset
; DX = cursor position
; CX = string length
; exit: nothing (BX, ES affected)
; history: 18-December-93 JRP created
; notes:
;-----------------------------------------------------------------------------
print proc near
push ax ; save affected registers
push bp
call get_cur ; needed to establish cursor position
lpout:
mov ah,0Eh ; write text function
mov al,cs:[bp] ; get next character
int VIDINT ; display it
inc bp ; advance the message pointer
loop lpout ; decrement cx and loop until 0
pop bp ; restore affected registers
pop ax
ret
print endp
;-----------------------------------------------------------------------------
; name: printch
; purpose: sends char in al to screen
; entry: AL = char
; exit: nothing
; history: 18-December-93
; notes:
;-----------------------------------------------------------------------------
printch proc near
push bx
mov ah,0Eh ; write text function
mov bh,0 ; use video page 0
int VIDINT
pop bx
ret
printch endp
;-----------------------------------------------------------------------------
; name: get_cur
; purpose: returns cursor position
; entry: nothing
; exit: DH = row DL = col (AX, BX affected)
; written: 18-December-93
; notes: assumes display page 0
;-----------------------------------------------------------------------------
get_cur proc near
push ax ; save affected registers
push bx
push cx ; (not interested in cursor's scan lines)
mov ah,03h ; get cursor position
mov bh,0 ; of page 0
pop cx ; restore affected registers
pop bx
pop ax
ret
get_cur endp
;-----------------------------------------------------------------------------
; name: flush
; purpose: gets rid of any extraneous characters from keyboard buffer
; entry: nothing
; exit: nothing
; history: 22-October-93 JRP created
; notes:
;-----------------------------------------------------------------------------
flush proc near
push ax ; save affected registers
push bx
push cx
push dx
mov cx,10h ; initialize loop counter
flushlp:
mov ah,1 ; get kbd status
int KBDINT
jz flushend ; zero means no character ready
mov ah,0 ; read character
int KBDINT
inc cx ; keep going until we get enough no chars
flushend:
loop flushlp ; only repeat so many times
pop dx ; restore affected registers
pop cx
pop bx
pop ax
ret
flush endp
;-----------------------------------------------------------------------------
; name: std_delay
; purpose: Standard delay interface for asm functions.
; entry: nothing
; exit: carry flag set on BIOS error
; author: Joseph R Power
; written: 11/3/93
;-----------------------------------------------------------------------------
std_delay proc near
push ax ; save reg
; turn off the speaker
in al,SPEAKER ; read 8255a-5 PPI interface port b status
or al,01 ; set bit 0: 8253 channel 2 clock input
and al,0fdh ; clear bit 1: disable the speaker
out SPEAKER,al ; write back to the port
; set the timer to start counting down when loaded
mov al,0b8h ; bits 7-6: 10 - channel 2
; bits 5-4: 11 - read/write lsb, msb
; bits 3-1: 100 - mode 4: countdown
; bit 0: 0 - binary counting
out TIMERSETUP,al ; setup 8253-5 timer as shown above
; set the timer to count down from DELAYTIME
mov ax,DELAYTIME ; load the count (lsb then msb)
out TIMER2,al
mov al,ah
out TIMER2,al
; now, loop until specified time is counted down
moretime:
mov al,88h ; latch the timer to read (channel 2)
out TIMERSETUP,al
in al,TIMER2 ; read lsb of current count-down time
in al,TIMER2 ; read msb of current count-down time
and al,080h ; repeat until counter wraps (when high bit
jz moretime ; goes to 1)
pop ax ; restore reg
ret
std_delay endp
;-----------------------------------------------------------------------------
; name: doshandler
; purpose: report whenever something tries to access dos
; entry: none
; exit: none
; history: 25-May-1994 JRP created
; notes:
;-----------------------------------------------------------------------------
doshandler proc near
push bp
push cx
push ax
push ds
mov ax,cs
mov ds,ax
mov bp,offset DOSMSG
mov cx,DOSMSGLEN
call print
xor ah,ah
int 16h
pop ds
pop ax
pop cx
pop bp
iret
doshandler endp
end init
Next, you will need something to compute and insert the checksum:
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
//
// COM2BIN.C
//
// authors: Chet Hosmer, Joseph Power
//
// Revisons: February 28,1993 - 1.0 created
// September 27,1995 - 1.2 modified for release on alt.lang.asm
//
// Purpose: This program creates the ROM image for BEEs
//
// Input: It requires two command line arguments as input:
//
// com2bin bee.bin romfile.bin
// | | |
// | | |__ Output file ready for rom burner
// | |__ BEE Loader Code (1st 1k of RMB ROM)
// |__ Executable
//
//
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
#include <stdio.h>
FILE *loader;
FILE *infile;
FILE *outfile;
unsigned char __far diagbuf[0x10000]; /* array must be big enough for 64K BEE */
main(int argc, char **argv)
{
unsigned char c;
unsigned int chksum;
unsigned long int bufptr, maxptr;
/* clear array */
for (bufptr = 0L; bufptr < 0x10000; bufptr++)
{
diagbuf[bufptr] = 0;
}
printf("BEE ROM Builder\n Version 1.2 Copyright 1994 Landmark Research Int'l
Corp. \n\n");
if (argc != 3)
{
printf("Invalid Input \n");
printf("%s loaderbinfile outputbinfile \n\n",argv[0]);
exit(-1);
}
loader = fopen(argv[1],"r+b"); // loader file name
outfile = fopen(argv[2],"w+b"); // output file name
printf("Putting the loader into the buffer.\n");
c = fgetc(loader);
bufptr = 0;
while (!feof(loader))
{
diagbuf[bufptr++] = c;
c = fgetc(loader);
}
maxptr = bufptr;
while (maxptr & 0x7FF)
{
++maxptr;
}
if (maxptr & 0x7FFF) maxptr = 0x8000;
printf("Computing checksum.\n");
chksum = 0;
for (bufptr = 0L; bufptr <= maxptr; ++bufptr)
{
chksum += diagbuf[bufptr];
}
chksum = chksum & 0x00ff;
/*
note: In this code the location of the checksum is at a fixed location.
You may want to compute its location.
*/
diagbuf[6] = -chksum;
printf("CheckSum = %02x \n\n", -chksum);
printf("Writing ROMFILE.BIN\n");
printf("Size: %8.8X\n",maxptr);
for (bufptr = 0L; bufptr < maxptr; bufptr++)
{
fputc(diagbuf[bufptr],outfile);
}
fclose(loader);
fclose(outfile);
}
Finally, you will want a makefile to simplify the process:
romfile.bin: bee.com com2bin.exe
@com2bin bee.com romfile.bin
bee.com: bee.asm
@ml /AT bee.asm
@del bee.obj
com2bin.exe: com2bin.c
@\c700\bin\cl /AL com2bin.c
@del com2bin.obj
As a test, run the makefile to create the file ROMFILE.BIN. Run you ROM
burner's software and load the file at offset 0 into the EPROM. You
will then have a BEE ready to be inserted into something that can
provide the address decode and buffering needed for the BIOS to "see"
the ROM. Our Kickstart IRQ card is perfect for this if you don't want
to design your own, but we're not talking rocket science here folks.
I have attempted to make this a good tutorial on how I create BEEs. I
hope it makes your life easier. You are free to use this code for
exploration. If you want to incorporate it in your own products I'm
sure the people who sign my paychecks would like to hear from you.
Joe Power
Sr Software Engineer
Quarterdeck Select Corp.