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.



