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 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.