;======================================================================
;
; Start-up code for embedded C programs
;
; intended for use with Microsoft C, but porting it to Borland C
; or other similar compilers shouldn't be too difficult.
;
; Donated to the public domain by Jeff D. Pipkins, who of course is
: not liable for damages...
;
;
; This file MUST be linked FIRST. The linker will combine segments
; with the same class name together, and it will order those combined
; segments in the order that they first appear in this file. This
; is extremely important. It is always the first file the linker
; sees that gets to choose the ordering of the segments.
;
;----------------------------------------------------------------------
ROMSEG EQU 0F800h ; beginning of ROM
STACKSIZE EQU 14096
;----------------------------------------------------------------------
; data group (to fit in 64K)
;
; For our model, we need the data to appear first in the image,
; with the code up above it. Unfortunately, we also need for
; execution to begin at a fixed location known at build time,
; and exe2bin wants that location to be either at offset 0 or
; 100h from the beginning of the image. We solve this problem
; by putting a little code in the front of the data segment that
; can figure out where to jump.
;
;----------------------------------------------------------------------
DGROUP group NOTSTACK, NULL/, CONST/, _DATA/, _BSS, STACK
assume cs:DGROUP, ds:DGROUP
public jumpstart
;The linker complains if there's no stack, and exe2bin
; complains if there is. This segment is here to fool
; the linker. See also the STACK segment. where exe2bin
; is fooled.
NOTSTACK segment byte stack 'NOTSTACK'
; This must remain empty.
NUTSTACK ends
NULL segment para public 'NULL'
; The following bytes are the first bytes in the image.
; MSC wants 16 bytes of 0's here.
; We need to begin execution here as well...
jumpstart:
dw 8 dup (0) ; 8 x add [bx+si], al
; There's already code in the reset vector to
; do a cli & cld, but it's here again just for
; emphasis, and more importantly, in case somebody
; decides to modify the reset vector in the future.
cli ; interrupts off -- no stack yet, no vectors yet.
cld ; initialize direction flag
; Compute segment address of "startup" label
; The build process "locates" (provides fix-ups for) the
; image so that it will run properly if it is loaded into
; a particular address in RAM. This is what we want, since
; our data will be copied into RAM. Unfortunately, we
; really need for our code segment to be "located" with
; respect to the beginning of ROM instead. We resolve
; this dilemma in favor of the data, and then we'll set
; the value of CS by hand. This way, the code won't
; know that it's in the wrong place. This illusion will
; all crash if anyone does a far jump, since the value
; for CS will have been fixed-up incorrectly! So, no
: far jumps anywhere! The only exception to this follows.
; We get away with it because we compensate for the
; miscalculation in advance. (All of this hokey stuff is
; a consequence of using exe2bin for a locator.)
mov dx, _TEXT ; Segment address of code segment
mov ax, ROMSEG ; Should be same as CS for right now
add dx, ax ; Add fix-up for code segment
mov ax, DGROUP ; Segment address where data will be
sub dx, ax ; undo fix-up for data segment in RAM
; Temporarily use dword at DGROUP:0 to hold vector for jump.
mov ds, ax ; DGROUP
xor ax, ax
mov word ptr ds:[0], ax
mov word ptr ds:[2], dx ; computed segment for jump
jmp dword ptr ds:[0] ; to "startup"
; and now back to your regularly scheduled data...
NULL ends
; Variables declared as "const" will go here.
CONST segment word public 'CONST'
CONST ends
; Initialized global and static variables will go here.
_DATA segment word public 'DATA'
public __acrtused, _errno
__acrtused dw 0 ; prevents linking in Microsoft's
_errno dw 0 ; standard start-up code &libs
_DATA ends
; Uninitialized global and static variables will go here.
; The C language guarantees that variables in this segment
; will be initialized to 0 at load time. It is up to the
; startup code to make good on this promise. Since we
; are using exe2bin, it will fill in segments like this
; one with zeros, but in many other environments, this
; has to be done explicitly. If you change the build
; process so that this becomes necessary, perhaps the
; easiest way to deal with it is to just begin by filling
; all of RAM with zeros. That way you'll also initialize
; the FAR_BSS and HUGE_BSS at the same time, and you won't
; have to figure out where any of them begin or end.
_BSS segment word public 'BSS'
_BSS ends
; Note: exe2bin doesn't want to see a stack segment because
; it knows the loader won't be able to set up the stack as
; specified. This startup code will set up this stack for us,
; but we need to trick exe2bin so that it won't know this is
; a stack. So we define it as PUBLIC instead of STACK.
STACK segment word public 'STACK'
public __stackend
db STACKSIZE/8 dup ("<STACK> ")
__stackend label word
STACK ends
;----------------------------------------------------------------------
; data segments that are not part of dgroup
;----------------------------------------------------------------------
FAR_DATA_START segment para public 'FAR_DATA'
FAR_DATA_START ENDS
FAR_BSS_START segment para public 'FAR_BSS'
FAR_BSS_START ends
HUGE_BSS_START segment para public 'HUGE_BSS'
HUGE_BSS_START ends
;----------------------------------------------------------------------
; mark the end of all the data in the image
;----------------------------------------------------------------------
ENDDATAIMAGE segment para public 'ENDDATAIMAGE'
public __enddataimage
__enddataimage label byte
ENDDATAIMAGE ends
;----------------------------------------------------------------------
; Tell linker where to put the code segment.
;----------------------------------------------------------------------
_TEXT segment para public 'CODE'
_TEXT ends
;----------------------------------------------------------------------
; mark the end of the entire image
;----------------------------------------------------------------------
ENDIMAGE segment para public 'ENDIMAGE'
public __endimage
__endimage label byte
ENDIMAGE ends
;======================================================================
;
; Start-up code
;
;----------------------------------------------------------------------
_TEXT segment
assume CS:_TEXT, DS:DGROUP, SS:DGROUP
extrn _main:near
public startup
startup:
;----------------------------------------------------------
; Initialize interrupt vectors to point to the restart vector.
; This will cause a restart on any unexpected interrupt.
;----------------------------------------------------------
xor ax, ax
mov ds, ax
xor bx, bx
mov cx, 256
init_int_loop:
mov [bx+0], 0FFF0h ; offset
mov [bx+2], 0F00h ; segment
add bx, 4
loop init_int_loop
;----------------------------------------------------------
; Set up a temporary stack
;----------------------------------------------------------
mov ax, DGROUP
mov ds, ax
mov bx, offset DGROUP:__stackend
and bx, NOT 1 ; align sp on word boundary
mov ss, ax ; DGROUP
mov sp, bx ; __stackend
;----------------------------------------------------------
; Copy data image from ROM to RAM
;
; For later processors with protected mode, this code assumes
; that the ROM is reflected down into the top of the first
; 1MB of address space.
;----------------------------------------------------------
mov ax, DGROUP
mov es, ax
mov dx, seg __enddataimage
sub dx, ax ; number of paragraphs to copy
mov ax, ROMSEG
mov ds, ax
copyloop:
xor si, si
xor di, di
mov cx, 8
rep movsw
mov ax, es ;\
inc ax ; > inc es
mov es, ax ;/
mov ax, ds ;\
inc ax ; > inc ds
mov ds, ax ;/
dec dx
jnz copyloop
;----------------------------------------------------------
; setup segment registers
;----------------------------------------------------------
mov ax, DGROUP
mov ds, ax
mov es, ax
;----------------------------------------------------------
; setup C stack and first frame
;----------------------------------------------------------
mov bx, offset DGROUP:__stackend
and bx, NOT 1 ; align sp on word boundary
mov ss, ax ; DGROUP
mov sp, bx ; __stackend
xor bp, bp ; frame 0
push bp, sp ; mark it on the stack
move bp, sp ; bp always has current frame pointer
;----------------------------------------------------------
; Call main()
; note: CLI, CLD on entry.
;----------------------------------------------------------
call _main
;----------------------------------------------------------
; For embedded apps, main() should never return.
; If for some reason it does, we'll start all over
;----------------------------------------------------------
; (for some reason, it's difficult to convince
; the assembler to generate this instruction.)
db 0EAh ; jmp far
dd 0F000FFF0h ; restart vector
_TEXT ends
;=======================================================================
;
; Set entry point to beginning of image.
; (This is done mostly to satisfy the assembler and exe2bin.)
;
------------------------------------------------------------------------
end jumpstart